Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

L'assegnazione dei parametri di input della procedura memorizzata alle variabili locali aiuta a ottimizzare la query?

Non cercherò di spiegare tutti i dettagli dello sniffing dei parametri, ma in breve, no, non è sempre aiuto (e può ostacolare).

Immagina una tabella (T) con una chiave primaria e una colonna Data indicizzata (A), nella tabella ci sono 1.000 righe, 400 hanno lo stesso valore di A (diciamo oggi 20130122), le restanti 600 righe sono i prossimi 600 giorni , quindi solo 1 record per data.

Questa domanda:

SELECT *
FROM T
WHERE A = '20130122';

Produrrà un piano di esecuzione diverso per:

SELECT *
FROM T
WHERE A = '20130123';

Poiché le statistiche indicheranno che per le prime 400 righe su 1.000 verranno restituite, l'ottimizzatore dovrebbe riconoscere che una scansione della tabella sarà più efficiente di una ricerca di segnalibri, mentre la seconda produrrà solo 1 riga, quindi una ricerca di segnalibri sarà molto più efficiente.

Ora, tornando alla tua domanda, se abbiamo reso questa una procedura:

CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param

Quindi esegui

EXECUTE dbo.GetFromT '20130122'; --400 rows

Verrà utilizzato il piano di query con la scansione della tabella, se la prima volta che lo esegui si utilizza "20130123" come parametro, memorizzerà il piano di ricerca dei segnalibri. Fino a quando la procedura non sarà ricompilata, il piano rimarrà lo stesso. Fare qualcosa del genere:

CREATE PROCEDURE dbo.GetFromT @Param VARCHAR(5)
AS
    DECLARE @Param2 VARCHAR(5) = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2

Quindi questo viene eseguito:

EXECUTE dbo.GetFromT '20130122';

Sebbene la procedura venga compilata in una volta sola, non scorre correttamente, quindi il piano di query creato alla prima compilazione non ha idea che @Param2 diventerà lo stesso di @param, quindi l'ottimizzatore (senza sapere quante righe aspettarsi) presupporrà che verrà restituito 300 (30%), in quanto tale riterrà una scansione della tabella più efficiente di una ricerca di un segnalibro. Se si esegue la stessa procedura con "20130123" come parametro, si otterrebbe lo stesso piano (indipendentemente dal parametro con cui è stato richiamato per la prima volta) perché le statistiche non possono essere utilizzate per un valore sconosciuto. Quindi eseguire questa procedura per '20130122' sarebbe più efficiente, ma per tutti gli altri valori sarebbe meno efficiente che senza parametri locali (supponendo che la procedura senza parametri locali sia stata prima richiamata con qualcosa di diverso da '20130122')

Alcune domande da dimostrare in modo da poter visualizzare personalmente i piani di esecuzione

Crea schema e dati di esempio

CREATE TABLE T (ID INT IDENTITY(1, 1) PRIMARY KEY, A DATE NOT NULL, B INT,C INT, D INT, E INT);

CREATE NONCLUSTERED INDEX IX_T ON T (A);

INSERT T (A, B, C, D, E)
SELECT  TOP 400 CAST('20130122' AS DATE), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   type = 'P'
UNION ALL
SELECT TOP 600 DATEADD(DAY, number, CAST('20130122' AS DATE)), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   Type = 'P';
GO
CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param
GO
CREATE PROCEDURE dbo.GetFromT2 @Param DATE
AS
    DECLARE @Param2 DATE = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2
GO

Procedure di esecuzione (che mostrano il piano di esecuzione effettivo):

EXECUTE GetFromT '20130122';
EXECUTE GetFromT '20130123';
EXECUTE GetFromT2 '20130122';
EXECUTE GetFromT2 '20130123';
GO
EXECUTE SP_RECOMPILE GetFromT;
EXECUTE SP_RECOMPILE GetFromT2;
GO
EXECUTE GetFromT '20130123';
EXECUTE GetFromT '20130122';
EXECUTE GetFromT2 '20130123';
EXECUTE GetFromT2 '20130122';

Lo vedrai la prima volta GetFromT viene compilato utilizza una scansione della tabella e la conserva quando viene eseguita con il parametro '20130122', GetFromT2 utilizza anche una scansione della tabella e conserva il piano per "20130122".

Dopo che le procedure sono state impostate per la ricompilazione ed eseguite nuovamente (notare in un ordine diverso), GetFromT utilizza un loopup di segnalibri e mantiene il piano per "20130122", nonostante in precedenza abbia ritenuto che una scansione della tabella sia un piano più appropriato. GetFromT2 non è interessato dall'ordine e ha lo stesso piano di prima della riconversione.

Quindi, in sintesi, dipende dalla distribuzione dei tuoi dati e dai tuoi indici, dalla frequenza di ricompilazione e da un po' di fortuna se una procedura trarrà vantaggio dall'utilizzo di variabili locali. Certamente non sempre aiuto.

Spero di aver fatto luce sull'effetto dell'utilizzo di parametri locali, piani di esecuzione e compilazione di stored procedure. Se ho fallito completamente o ho perso un punto chiave, una spiegazione molto più approfondita può essere trovata qui:

http://www.sommarskog.se/query-plan-mysteries.html