Come ottimizzare le query


9

Ho una struttura di database simile a questa,

CREATE TABLE [dbo].[Dispatch](
    [DispatchId] [int] NOT NULL,
    [ContractId] [int] NOT NULL,
    [DispatchDescription] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Dispatch] PRIMARY KEY CLUSTERED 
(
    [DispatchId] ASC,
    [ContractId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[DispatchLink](
    [ContractLink1] [int] NOT NULL,
    [DispatchLink1] [int] NOT NULL,
    [ContractLink2] [int] NOT NULL,
    [DispatchLink2] [int] NOT NULL
) ON [PRIMARY]

GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (1, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (2, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (3, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (4, 1, N'Test')
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 2)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 3)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 3, 1, 2)
GO

Lo scopo della tabella DispatchLink è collegare due record Dispatch insieme. A proposito, sto usando una chiave primaria composita sulla mia tabella di invio a causa dell'eredità, quindi non posso cambiarla senza molto dolore. Inoltre, la tabella dei collegamenti potrebbe non essere il modo corretto per farlo? Ma ancora un'eredità.

Quindi la mia domanda, se eseguo questa query

select * from Dispatch d
inner join DispatchLink dl on d.DispatchId = dl.DispatchLink1 and d.ContractId = dl.ContractLink1
or d.DispatchId = dl.DispatchLink2 and d.ContractId = dl.ContractLink2

Non riesco mai a farlo per cercare un indice nella tabella DispatchLink. Esegue sempre una scansione dell'indice completa. Questo va bene con alcuni record, ma quando hai 50000 in quella tabella, scansiona 50000 record nell'indice secondo il piano di query. È perché ci sono 'ands' e 'ors' nella clausola join, ma non riesco a capire perché SQL non può fare un paio di ricerche sull'indice, una per il lato sinistro di 'o', e uno per il lato destro del 'o'.

Vorrei una spiegazione per questo, non un suggerimento per rendere la query più veloce a meno che ciò non possa essere fatto senza modificare la query. Il motivo è che sto usando la query sopra come filtro join di replica unione, quindi purtroppo non posso semplicemente aggiungere un altro tipo di query.

AGGIORNAMENTO: ad esempio questi sono i tipi di indici che ho aggiunto,

CREATE NONCLUSTERED INDEX IDX1 ON DispatchLink (ContractLink1, DispatchLink1)
CREATE NONCLUSTERED INDEX IDX2 ON DispatchLink (ContractLink2, DispatchLink2)
CREATE NONCLUSTERED INDEX IDX3 ON DispatchLink (ContractLink1, DispatchLink1, ContractLink2, DispatchLink2)

Quindi utilizza gli indici, ma esegue una scansione dell'indice nell'intero indice, quindi 50000 record esegue la scansione di 50000 record nell'indice.


Hai qualche indice sul DispatchLinktavolo?
ypercubeᵀᴹ

Ho aggiunto gli indici che ho provato sopra.
peter

Nella tua query: "seleziona * da Dispatch d inner join DispatchLink dl su d.DispatchId = dl.DispatchLink1 e d.ContractId = dl.ContractLink1 o d.DispatchId = dl.DispatchLink2 e d.ContractId = dl.ContractLink2" prova a rimuovere la condizione "OR" e sostituirla con UNION di 2 istruzioni SELECT ciascuna senza utilizzare "OR", utilizzare anche le uniche colonne chiave in entrambi i SELECT anziché "*", solo per rendere il test il più puro possibile.
NoChance,

Grazie SQL Kiwi, questo è qualcosa che ho provato in precedenza ma purtroppo non ha funzionato.
Pietro,

1
È possibile avere problemi di replica con una query più semplice: selezionare * da Dispatch d inner join DispatchLink dl on d.DispatchId = dl.DispatchLink1 e d.ContractId = dl.ContractLink1 Se sì, possiamo duplicare i dati in DispatchLink in modo che i risultati siano ancora validi ...
AK,

Risposte:


12

L'ottimizzatore può considerare molte alternative di piano (comprese quelle con più ricerche) ma per disgiunzioni ( ORpredicati) non considera i piani che coinvolgono intersezioni di indice per impostazione predefinita. Dati gli indici:

CREATE CLUSTERED INDEX cx 
ON dbo.DispatchLink (DispatchLink1, ContractLink1);

CREATE NONCLUSTERED INDEX nc1 
ON dbo.DispatchLink (DispatchLink2, ContractLink2);

Possiamo forzare la ricerca di indici (supponendo che SQL Server 2008 o successivo):

SELECT * 
FROM dbo.Dispatch AS d
INNER JOIN dbo.DispatchLink AS dl WITH (FORCESEEK) ON 
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

Piano FORCESEEK

Utilizzando i dati di esempio, il piano di ricerca costa a 0,0332551 unità rispetto a 0,0068057 per il piano di scansione:

Piano di scansione

Esistono tutti i tipi di possibili riscritture e suggerimenti per le query che possiamo provare. Un esempio di riscrittura per promuovere un'opzione che l'ottimizzatore non considera per il piano originale è:

SELECT * 
FROM dbo.Dispatch AS d
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;

Questo piano di esecuzione non cerca il secondo indice se trova una corrispondenza sul primo:

APPLICARE il piano TOP

Ciò potrebbe comportare prestazioni leggermente migliori rispetto al FORCESEEKpiano predefinito .

Senza aggiungere nuovi indici, possiamo anche forzare una ricerca nella tabella Dispatch:

SELECT * 
FROM dbo.DispatchLink AS dl
JOIN dbo.Dispatch AS d WITH (FORCESEEK) ON
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

Cerca 2

Questo può essere migliore o peggiore del primo esempio a seconda di cose come quante righe ci sono in ciascuna delle tabelle. Il APPLY + TOPmiglioramento è ancora possibile:

SELECT * 
FROM dbo.DispatchLink AS dl
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;

Questa è una risposta molto utile. Ho fatto un'altra domanda dba.stackexchange.com/questions/23773/analysing-a-query-plan che mostra il piano di query effettivo su dati reali (non i miei dati di test). Non ho le conoscenze per capire esattamente quale sia il collo di bottiglia nel piano di query. Forse puoi dare un'occhiata?
peter

È davvero interessante perché l'aggiunta di 'FORCESEEK' fa funzionare la mia query in 9 secondi anziché impiegare più di 10 minuti. Le statistiche di aggiornamento non fanno differenza. Perché altrimenti l'analizzatore di query potrebbe sbagliare così tanto?
Pietro,

Penso che tu abbia ragione riguardo al design. Cosa intendi con ripetizione di colonne? Come progetteresti una struttura di tabella che dovrebbe collegare due record di spedizione come correlati? Per chiarire se la tabella 'reale' ha il proprio campo chiave primaria, ma sì avere una chiave composita in Dispatch non aiuta esattamente.
Pietro,

Kiwi SQL. Ripetizione di colonne. Capito grazie.
peter
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.