Come ottimizzare la query T-SQL utilizzando il piano di esecuzione


15

Ho una query SQL che ho trascorso negli ultimi due giorni cercando di ottimizzare utilizzando la versione di prova ed errore e il piano di esecuzione, ma senza risultati. Per favore, perdonami per averlo fatto, ma pubblicherò qui l'intero piano di esecuzione. Ho fatto lo sforzo di rendere generici i nomi di tabella e colonna nella query e nel piano di esecuzione sia per brevità sia per proteggere l'IP della mia azienda. Il piano di esecuzione può essere aperto con SQL Sentry Plan Explorer .

Ho fatto una discreta quantità di T-SQL, ma l'utilizzo dei piani di esecuzione per ottimizzare la mia query è una nuova area per me e ho davvero cercato di capire come farlo. Quindi, se qualcuno potesse aiutarmi con questo e spiegare come questo piano di esecuzione possa essere decifrato per trovare modi nella query per ottimizzarlo, sarei eternamente grato. Ho molte più domande da ottimizzare: ho solo bisogno di un trampolino di lancio per aiutarmi con questo primo.

Questa è la domanda:

DECLARE @Param0 DATETIME     = '2013-07-29';
DECLARE @Param1 INT          = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50)  = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50)  = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50)  = 'Text3';

SET NOCOUNT ON

DECLARE @MyTableVar TABLE
(
    B_Var1_PK int,
    Job_Var1 varchar(512),
    Job_Var2 varchar(50)
)

INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2) 
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);

CREATE TABLE #TempTable
(
    TTVar1_PK INT PRIMARY KEY,
    TTVar2_LK VARCHAR(100),
    TTVar3_LK VARCHAR(50),
    TTVar4_LK INT,
    TTVar5 VARCHAR(20)
);

INSERT INTO #TempTable
SELECT DISTINCT
    T.T1_PK,
    T.T1_Var1_LK,
    T.T1_Var2_LK,
    MAX(T.T1_Var3_LK),
    T.T1_Var4_LK
FROM
    MyTable1 T
    INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
    INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK

-- This is the slow statement...
SELECT 
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK,
    C.C_Var1_PK,
    SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
    INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK  = T.TTVar1_PK
    INNER JOIN @MyTableVar     B ON B.B_Var1_PK     = A.Job
    INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK     = A.A_Var5_FK_LK
    INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK     = A.A_Var6_FK_LK
    INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK     = A.A_Var7_FK_LK  
    LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK 
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK, 
    C.C_Var1_PK


IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END

Quello che ho scoperto è che la terza affermazione (commentata come lenta) è la parte che richiede più tempo. Le due dichiarazioni prima ritornano quasi all'istante.

Il piano di esecuzione è disponibile come XML a questo link .

Meglio fare clic con il pulsante destro del mouse e salvare, quindi aprirlo in SQL Sentry Plan Explorer o in qualche altro software di visualizzazione piuttosto che aprirlo nel browser.

Se hai bisogno di ulteriori informazioni da me sulle tabelle o sui dati, non esitare a chiedere.


2
Le tue statistiche sono lontane. Quando è stata l'ultima volta che hai frammentato gli indici o aggiornato le statistiche? Inoltre, proverei a utilizzare una tabella temporanea, anziché la variabile di tabella, @MyTableVar, poiché l'ottimizzatore non può davvero utilizzare le statistiche sulle variabili di tabella.
Adam Haines,

Grazie per la tua risposta Adam. La modifica di @MyTableVar in una tabella temporanea non ha alcun effetto, ma è solo un piccolo numero di righe (che è possibile visualizzare dal piano di esecuzione). Cosa nel piano di esecuzione mostra che le mie statistiche sono lontane? Indica quali indici devono essere riorganizzati o ricostruiti e quali tabelle devono avere le statistiche aggiornate?
Neo

3
Quel hash join in basso a destra ha un valore stimato di 24.000 righe nell'input della build ma 3.285.620 effettivi potrebbero quindi essere versati tempdb. cioè le stime per le righe risultanti dall'unione tra TableAe @MyTableVarsono lontane. Anche il numero di righe che vanno in una specie è molto maggiore del previsto, quindi potrebbero anche essere versate.
Martin Smith,

Risposte:


21

Prima di arrivare alla risposta principale, è necessario aggiornare due software.

Aggiornamenti software richiesti

Il primo è SQL Server. Si esegue SQL Server 2008 Service Pack 1 (build 2531). Dovresti essere aggiornato almeno al Service Pack corrente (SQL Server 2008 Service Pack 3 - build 5500). La build più recente di SQL Server 2008 al momento della scrittura è Service Pack 3, aggiornamento cumulativo 12 (build 5844).

Il secondo software è SQL Sentry Plan Explorer . Le ultime versioni hanno nuove importanti funzionalità e correzioni, inclusa la possibilità di caricare direttamente un piano di query per analisi di esperti (non è necessario incollare XML ovunque!)

Analisi del piano di query

La stima della cardinalità per la variabile della tabella è esatta, grazie a una ricompilazione a livello di istruzione:

stima delle variabili di tabella

Sfortunatamente, le variabili di tabella non mantengono le statistiche di distribuzione, quindi tutto l'ottimizzatore sa che ci sono sei righe; non sa nulla dei valori che potrebbero essere in quelle sei righe. Questa informazione è cruciale dato che l'operazione successiva è un join a un'altra tabella. La stima della cardinalità da quel join si basa su un'ipotesi sfrenata da parte dell'ottimizzatore:

prima stima del join

Da quel momento in poi, il piano scelto dall'ottimizzatore si basa su informazioni errate, quindi non c'è da meravigliarsi che le prestazioni siano così scarse. In particolare, la memoria riservata alle specie e alle tabelle hash per i join hash sarà troppo piccola. Al momento dell'esecuzione, i tipi di overflow e le operazioni di hashing verranno riversati sul disco tempdb fisico .

SQL Server 2008 non lo evidenzia nei piani di esecuzione; è possibile monitorare gli sversamenti utilizzando Eventi estesi o Avvisi ordinamento profili e Avvisi hash . La memoria è riservata per ordinamenti e hash in base alle stime di cardinalità prima dell'avvio dell'esecuzione e non può essere aumentata durante l'esecuzione, indipendentemente dalla quantità di memoria di riserva di SQL Server. Stime accurate del conteggio delle righe sono quindi cruciali per qualsiasi piano di esecuzione che coinvolge operazioni che consumano memoria nell'area di lavoro.

Anche la tua query è parametrizzata. Si consiglia di aggiungere OPTION (RECOMPILE)alla query se valori di parametro diversi influenzano il piano di query. Probabilmente dovresti considerare di usarlo comunque, quindi l'ottimizzatore può vedere il valore di @Param1al momento della compilazione. Se non altro, questo può aiutare l'ottimizzatore a produrre una stima più ragionevole per la ricerca dell'indice mostrata sopra, dato che la tabella è molto grande e partizionata. Può anche consentire l'eliminazione della partizione statica.

Prova di nuovo la query con una tabella temporanea anziché con la variabile table e OPTION (RECOMPILE) . Dovresti anche provare a materializzare il risultato del primo join in un'altra tabella temporanea ed eseguire il resto della query su quello. Il numero di righe non è poi così grande (3.285.620), quindi questo dovrebbe essere ragionevolmente veloce. L'ottimizzatore avrà quindi una stima della cardinalità esatta e statistiche di distribuzione per il risultato del join. Per fortuna, il resto del piano andrà a buon fine.

A partire dalle proprietà mostrate nel piano, la query materializzante sarebbe:

SELECT
    A.A_Var7_FK_LK,
    A.A_Var4_FK_LK,
    A.A_Var6_FK_LK, 
    A.A_Var5_FK_LK,
    A.A_Var1,
    A.A_Var2,
    A.A_Var3_FK_LK
INTO #AnotherTempTable
FROM @MyTableVar AS B
JOIN TableA AS A
    ON A.Job = B.B_Var1_PK
WHERE
    A_Var8_FK_LK = @Param1;

Potresti anche INSERTentrare in una tabella temporanea predefinita (i tipi di dati corretti non sono mostrati nel piano, quindi non posso fare quella parte). La nuova tabella temporanea può o meno beneficiare di indici cluster e non cluster.


Grazie mille per questa risposta approfondita. Mi dispiace che ci sia voluta una settimana per rispondere - ci ho lavorato ogni giorno intervallato da altri lavori. Ho implementato i tuoi suggerimenti materializzando l'adesione a TableA in #AnotherTempTable. Sembra che ciò abbia avuto il miglior impatto: gli altri suggerimenti (l'utilizzo di una tabella temporanea anziché una variabile di tabella per @MyTableVar e l'utilizzo OPTION (RECOMPILE)non ha avuto molto effetto o alcuno. Gli "Anonimi" e "Pubblica su SQLPerformance.com" le opzioni in SQL Sentry Plan Explorer sono fantastiche: le ho appena usate: answer.sqlperformance.com/questions/1087
Neo

-6

Ho notato che dovrebbe esserci un PK su @MyTableVar e concordo sul fatto che #MyTableVar ha spesso prestazioni migliori (specialmente con un numero maggiore di righe).

La condizione all'interno della clausola where

   WHERE A.A_Var8_FK_LK = @Param1

dovrebbe essere spostato nell'unione interna A AND. L'ottimizzatore non è abbastanza intelligente nella mia esperienza per farlo (scusate non ho visto il piano) e può fare una grande differenza.

Se tali modifiche non mostrano miglioramenti, creerei successivamente un'altra tabella temporanea di A e tutte le cose a cui è vincolata (bene?) Da A.A_Var8_FK_LK = @ Param1 se quel raggruppamento ha un senso logico per te.

Quindi creare un indice cluster su quella tabella temporanea (prima o dopo la creazione) per la condizione di join successiva.

Quindi unisci quel risultato ai pochi tavoli (F e T) che rimangono.

Bam, che ha bisogno di un piano di query puzzolente quando le stime delle righe sono disattivate e talvolta non sono facilmente migliorabili ). Suppongo che tu abbia degli indici corretti, che è quello che vorrei verificare prima all'interno del piano.

Una traccia può mostrare le fuoriuscite di tempdb che possono o meno avere un impatto drastico.

Un altro approccio alternativo - almeno più veloce da provare - è quello di ordinare le tabelle dal numero più basso di righe (A) al più alto e quindi iniziare ad aggiungere l'unione, l'hash e il ciclo ai join. Quando sono presenti suggerimenti, l'ordine di join viene fissato come specificato. Altri utenti evitano saggiamente questo approccio perché può danneggiare a lungo termine se i conteggi delle righe relative cambiano radicalmente. È auspicabile un numero minimo di suggerimenti.

Se stai facendo molti di questi, forse un ottimizzatore commerciale merita una prova (o prova) ed è ancora una buona esperienza di apprendimento.


Sì. Assicura che le righe restituite da A siano limitate dal vincolo. Altrimenti l'ottimizzatore potrebbe unirsi per primo e applicare il vincolo in seguito. Ne ho a che fare ogni giorno.
crokusek,

4
@crokusek Ti sbagli di grosso. L'ottimizzatore di SQL Server è abbastanza bravo a sapere che le query sono equivalenti (se una condizione è in WHERE o la clausola ON) quando si tratta di un join INNER.
ypercubeᵀᴹ

6
Potresti trovare utili le serie di Paul White su Query Optimizer .
Martin Smith,

È un'abitudine terribile. Forse sarà per questo caso particolare (dove c'è un vincolo) ma vengo dal paese di più sviluppatori che si accumula su condizioni AND nella clausola where. SQL Server non li "sposta" costantemente nel join per te.
crokusek,

Accetto errato per i join esterni (e giusti). Ma quando ci sono solo espressioni AND all'interno di una clausola where e ogni termine corrisponde esclusivamente a un join interno specifico, quel termine può essere spostato in modo sicuro e sicuro nella posizione "on" come ottimizzazione e best practice (imo). Che si tratti di una condizione di unione "vera" o solo di un vincolo fisso, è secondario a un notevole aumento delle prestazioni. Quel collegamento è per un caso banale. La vita reale ha molteplici condizioni in cui si trovano convert () e matematica e tali da renderli candidati migliori da cui derivare le migliori pratiche.
crokusek,
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.