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

Sniffing dei parametri, Incorporamento e le opzioni RECOMPILE

Sniffing dei parametri

La parametrizzazione delle query promuove il riutilizzo dei piani di esecuzione memorizzati nella cache, evitando così compilazioni non necessarie e riducendo il numero di query ad hoc nella cache dei piani.

Queste sono tutte cose buone, fornite la query parametrizzata dovrebbe davvero utilizzare lo stesso piano di esecuzione memorizzato nella cache per valori di parametro diversi. Un piano di esecuzione efficiente per un valore di parametro potrebbe non essere una buona scelta per altri possibili valori di parametro.

Quando lo sniffing dei parametri è abilitato (impostazione predefinita), SQL Server sceglie un piano di esecuzione in base ai valori dei parametri particolari esistenti al momento della compilazione. Il presupposto implicito è che le istruzioni parametrizzate vengano eseguite più comunemente con i valori dei parametri più comuni. Questo suona abbastanza ragionevole (anche ovvio) e in effetti spesso funziona bene.

Può verificarsi un problema quando si verifica una ricompilazione automatica del piano memorizzato nella cache. Una ricompilazione può essere attivata per tutti i tipi di motivi, ad esempio perché un indice utilizzato dal piano memorizzato nella cache è stato eliminato (una correttezza ricompilazione) o perché le informazioni statistiche sono cambiate (una ottimalità ricompilare).

Qualunque sia la causa esatta della ricompilazione del piano, esiste la possibilità che sia un atipico value viene passato come parametro al momento della generazione del nuovo piano. Ciò può comportare un nuovo piano memorizzato nella cache (basato sul valore del parametro atipico sniffato) che non va bene per la maggior parte delle esecuzioni per le quali verrà riutilizzato.

Non è facile prevedere quando un particolare piano di esecuzione verrà ricompilato (per esempio perché le statistiche sono sufficientemente cambiate) determinando una situazione in cui un piano riutilizzabile di buona qualità può essere improvvisamente sostituito da un piano abbastanza diverso ottimizzato per valori di parametri atipici.

Uno di questi scenari si verifica quando il valore atipico è altamente selettivo, risultando in un piano ottimizzato per un numero ridotto di righe. Tali piani utilizzeranno spesso l'esecuzione a thread singolo, i join di loop nidificati e le ricerche. Possono sorgere seri problemi di prestazioni quando questo piano viene riutilizzato per valori di parametri diversi che generano un numero molto maggiore di righe.

Disabilitazione dello sniffing dei parametri

Lo sniffing dei parametri può essere disabilitato utilizzando il flag di traccia documentato 4136. Il flag di traccia è supportato anche per per-query utilizzare tramite il QUERYTRACEON suggerimento per la query. Entrambi si applicano a partire da SQL Server 2005 Service Pack 4 (e leggermente prima se si applicano aggiornamenti cumulativi al Service Pack 3).

A partire da SQL Server 2016, lo sniffing dei parametri può essere disabilitato anche a livello di database , utilizzando il PARAMETER_SNIFFING argomento per ALTER DATABASE SCOPED CONFIGURATION .

Quando lo sniffing dei parametri è disabilitato, SQL Server utilizza la distribuzione media statistiche per scegliere un piano di esecuzione.

Anche questo suona come un approccio ragionevole (e può aiutare a evitare la situazione in cui il piano è ottimizzato per un valore di parametro insolitamente selettivo), ma non è nemmeno una strategia perfetta:un piano ottimizzato per un valore "medio" potrebbe benissimo finire per essere seriamente non ottimale per i valori dei parametri comunemente visti.

Considera un piano di esecuzione che contiene operatori che consumano memoria come ordina e hash. Poiché la memoria è riservata prima dell'inizio dell'esecuzione della query, un piano parametrizzato basato su valori di distribuzione medi può riversarsi su tempdb per valori di parametri comuni che producono più dati di quelli previsti dall'ottimizzatore.

Le prenotazioni di memoria in genere non possono aumentare durante l'esecuzione della query, indipendentemente dalla quantità di memoria libera che il server potrebbe avere. Alcune applicazioni traggono vantaggio dalla disattivazione dello sniffing dei parametri (vedi questo post di archivio del Dynamics AX Performance Team per un esempio).

Per la maggior parte dei carichi di lavoro, disabilitare completamente lo sniffing dei parametri è la soluzione sbagliata , e potrebbe anche essere un disastro. Lo sniffing dei parametri è un'ottimizzazione euristica:funziona meglio dell'utilizzo di valori medi sulla maggior parte dei sistemi, la maggior parte delle volte.

Suggerimenti per le query

SQL Server offre una gamma di suggerimenti per le query e altre opzioni per ottimizzare il comportamento dello sniffing dei parametri:

  • Il OPTIMIZE FOR (@parameter = value) il suggerimento per la query crea un piano riutilizzabile basato su un valore specifico.
  • OPTIMIZE FOR (@parameter UNKNOWN) utilizza le statistiche di distribuzione media per un particolare parametro.
  • OPTIMIZE FOR UNKNOWN utilizza la distribuzione media per tutti i parametri (stesso effetto del flag di traccia 4136).
  • Il WITH RECOMPILE l'opzione stored procedure compila un nuovo piano di procedure per ogni esecuzione.
  • Il OPTION (RECOMPILE) il suggerimento per la query compila un nuovo piano per una singola istruzione.

La vecchia tecnica di "nascondere i parametri" (assegnando parametri di procedura a variabili locali e facendo invece riferimento alle variabili) ha lo stesso effetto di specificare OPTIMIZE FOR UNKNOWN . Può essere utile su istanze precedenti a SQL Server 2008 (il OPTIMIZE FOR suggerimento era nuovo per il 2008).

Si potrebbe sostenere che ogni l'istruzione parametrizzata dovrebbe essere verificata per la sensibilità ai valori dei parametri e lasciata in pace (se il comportamento predefinito funziona bene) o suggerita esplicitamente utilizzando una delle opzioni precedenti.

Nella pratica ciò viene fatto raramente, in parte perché l'esecuzione di un'analisi completa per tutti i possibili valori dei parametri può richiedere molto tempo e competenze piuttosto avanzate.
Molto spesso, tale analisi non viene eseguita e i problemi di sensibilità ai parametri vengono affrontati come e quando si verificano in produzione.

Questa mancanza di analisi preventiva è probabilmente una delle ragioni principali per cui lo sniffing dei parametri ha una scarsa reputazione. Vale la pena essere consapevoli del potenziale insorgere di problemi ed eseguire almeno una rapida analisi sulle affermazioni che possono causare problemi di prestazioni se ricompilate con un valore di parametro atipico.

Cos'è un parametro?

Alcuni direbbero che un SELECT L'istruzione che fa riferimento a una variabile locale è una "istruzione parametrizzata" di sorta, ma questa non è la definizione utilizzata da SQL Server.

Un'indicazione ragionevole che un'istruzione utilizza parametri può essere trovata osservando le proprietà del piano (vedere i Parametri scheda in Sentry One Plan Explorer. Oppure fai clic sul nodo radice del piano di query in SSMS, apri le Proprietà finestra ed espandere l'Elenco parametri nodo):

Il "valore compilato" mostra il valore sniffato del parametro utilizzato per compilare il piano memorizzato nella cache. Il 'valore di runtime' mostra il valore del parametro sulla particolare esecuzione catturata nel piano.

Ognuna di queste proprietà può essere vuota o mancante in circostanze diverse. Se una query non è parametrizzata, le proprietà saranno semplicemente tutte mancanti.

Solo perché nulla è mai semplice in SQL Server, ci sono situazioni in cui l'elenco dei parametri può essere popolato, ma l'istruzione non è ancora parametrizzata. Ciò può verificarsi quando SQL Server tenta una semplice parametrizzazione (discussa in seguito) ma decide che il tentativo non è sicuro. In tal caso saranno presenti i marker di parametro, ma il piano di esecuzione non è infatti parametrizzato.

Lo sniffing non è solo per le stored procedure

Lo sniffing dei parametri si verifica anche quando un batch è parametrizzato in modo esplicito per il riutilizzo utilizzando sp_executesql .

Ad esempio:

EXECUTE sys.sp_executesql
    N'
    SELECT
        P.ProductID,
        P.Name,
        TotalQty = SUM(TH.Quantity)
    FROM Production.Product AS P
    JOIN Production.TransactionHistory AS TH
        ON TH.ProductID = P.ProductID
    WHERE
        P.Name LIKE @NameLike
    GROUP BY
        P.ProductID,
        P.Name;
    ',
    N'@NameLike nvarchar(50)',
    @NameLike = N'K%';

L'ottimizzatore sceglie un piano di esecuzione in base al valore sniffato di @NameLike parametro. Si stima che il valore del parametro "K%" corrisponda a pochissime righe nel Product tabella, in modo che l'ottimizzatore scelga un join del ciclo nidificato e una strategia di ricerca delle chiavi:

Eseguendo nuovamente l'istruzione con un valore di parametro di "[H-R]%" (che corrisponderà a molte più righe) riutilizza il piano parametrizzato memorizzato nella cache:

EXECUTE sys.sp_executesql
    N'
    SELECT
        P.ProductID,
        P.Name,
        TotalQty = SUM(TH.Quantity)
    FROM Production.Product AS P
    JOIN Production.TransactionHistory AS TH
        ON TH.ProductID = P.ProductID
    WHERE
        P.Name LIKE @NameLike
    GROUP BY
        P.ProductID,
        P.Name;
    ',
    N'@NameLike nvarchar(50)',
    @NameLike = N'[H-R]%';

AdventureWorks il database di esempio è troppo piccolo per renderlo un disastro in termini di prestazioni, ma questo piano non è certamente ottimale per il valore del secondo parametro.

Possiamo vedere il piano che l'ottimizzatore avrebbe scelto svuotando la cache del piano ed eseguendo nuovamente la seconda query:

Con un numero maggiore di corrispondenze previste, l'ottimizzatore determina che un hash join e un'aggregazione hash sono strategie migliori.

Funzioni T-SQL

Lo sniffing dei parametri si verifica anche con le funzioni T-SQL, anche se il modo in cui vengono generati i piani di esecuzione può renderlo più difficile da vedere.

Ci sono buone ragioni per evitare le funzioni scalari T-SQL e multi-istruzione in generale, quindi solo per scopi didattici, ecco una versione della funzione T-SQL con valori di tabella multi-istruzione della nostra query di test:

CREATE FUNCTION dbo.F
    (@NameLike nvarchar(50))
RETURNS @Result TABLE
(
    ProductID   integer NOT NULL PRIMARY KEY,
    Name        nvarchar(50) NOT NULL,
    TotalQty    integer NOT NULL
)
WITH SCHEMABINDING
AS
BEGIN
    INSERT @Result
    SELECT
        P.ProductID,
        P.Name,
        TotalQty = SUM(TH.Quantity)
    FROM Production.Product AS P
    JOIN Production.TransactionHistory AS TH
        ON TH.ProductID = P.ProductID
    WHERE
        P.Name LIKE @NameLike
    GROUP BY
        P.ProductID,
        P.Name;
 
    RETURN;
END;

La seguente query utilizza la funzione per visualizzare le informazioni per i nomi dei prodotti che iniziano con 'K':

SELECT
    Result.ProductID,
    Result.Name,
    Result.TotalQty
FROM dbo.F(N'K%') AS Result;

È più difficile visualizzare lo sniffing dei parametri con una funzione incorporata perché SQL Server non restituisce un piano di query post-esecuzione (effettivo) separato per ogni chiamata di funzione. La funzione potrebbe essere chiamata più volte all'interno di una singola istruzione e gli utenti non rimarrebbero colpiti se SSMS tentasse di visualizzare un milione di piani di chiamata di funzione per una singola query.

Come risultato di questa decisione di progettazione, il piano effettivo restituito da SQL Server per la nostra query di test non è molto utile:

Tuttavia, ci sono modi per vedere lo sniffing dei parametri in azione con le funzioni integrate. Il metodo che ho scelto di utilizzare qui è ispezionare la cache del piano:

SELECT
    DEQS.plan_generation_num,
    DEQS.execution_count,
    DEQS.last_logical_reads,
    DEQS.last_elapsed_time,
    DEQS.last_rows,
    DEQP.query_plan
FROM sys.dm_exec_query_stats AS DEQS
CROSS APPLY sys.dm_exec_sql_text(DEQS.plan_handle) AS DEST
CROSS APPLY sys.dm_exec_query_plan(DEQS.plan_handle) AS DEQP
WHERE
    DEST.objectid = OBJECT_ID(N'dbo.F', N'TF');

Questo risultato mostra che il piano delle funzioni è stato eseguito una volta, al costo di 201 letture logiche con 2891 microsecondi trascorsi, e l'esecuzione più recente ha restituito una riga. La rappresentazione del piano XML restituita mostra che il valore del parametro era annusato:

Ora esegui di nuovo l'istruzione, con un parametro diverso:

SELECT
    Result.ProductID,
    Result.Name,
    Result.TotalQty
FROM dbo.F(N'[H-R]%') AS Result;

Il piano di post-esecuzione mostra che 306 righe sono state restituite dalla funzione:

La query della cache del piano mostra che il piano di esecuzione memorizzato nella cache per la funzione è stato riutilizzato (execution_count =2):

Mostra anche un numero molto più elevato di letture logiche e un tempo trascorso più lungo rispetto all'esecuzione precedente. Ciò è coerente con il riutilizzo di cicli annidati e piano di ricerca, ma per essere completamente sicuri, il piano della funzione post-esecuzione può essere acquisito utilizzando Eventi estesi o SQL Server Profiler strumento:

Poiché lo sniffing dei parametri si applica alle funzioni, questi moduli possono subire gli stessi cambiamenti imprevisti nelle prestazioni comunemente associati alle stored procedure.

Ad esempio, la prima volta che si fa riferimento a una funzione, è possibile che venga memorizzato nella cache un piano che non utilizza il parallelismo. Le esecuzioni successive con valori di parametro che trarrebbero vantaggio dal parallelismo (ma riutilizzano il piano seriale memorizzato nella cache) mostreranno prestazioni inaspettatamente scarse.

Questo problema può essere difficile da identificare perché SQL Server non restituisce piani di post-esecuzione separati per le chiamate di funzione, come abbiamo visto. Utilizzo di Eventi estesi o Profiler acquisire regolarmente i piani post-esecuzione può essere estremamente dispendioso in termini di risorse, quindi spesso ha senso utilizzare quella tecnica in modo molto mirato. Le difficoltà relative al debug dei problemi di sensibilità ai parametri della funzione significano che è ancora più utile fare un'analisi (e codificare in modo difensivo) prima che la funzione entri in produzione.

Lo sniffing dei parametri funziona esattamente allo stesso modo con le funzioni scalari definite dall'utente T-SQL (a meno che non siano in linea, da SQL Server 2019 in poi). Le funzioni in linea con valori di tabella non generano un piano di esecuzione separato per ogni chiamata, perché (come dice il nome) queste sono integrate nella query chiamante prima della compilazione.

Attenzione ai NULL annusati

Svuota la cache del piano e richiedi una stima (pre-esecuzione) piano per la query di test:

SELECT
    Result.ProductID,
    Result.Name,
    Result.TotalQty
FROM dbo.F(N'K%') AS Result;

Vedrai due piani di esecuzione, il secondo dei quali è per la chiamata di funzione:

Una limitazione dello sniffing dei parametri con funzioni incorporate nei piani stimati significa che il valore del parametro viene sniffato come NULLs (non "K%"):

Nelle versioni di SQL Server precedenti al 2012, questo piano (ottimizzato per un NULLs parametro) è memorizzato nella cache per il riutilizzo . Questo è un peccato, perché NULLs è improbabile che sia un valore di parametro rappresentativo e non era certamente il valore specificato nella query.

SQL Server 2012 (e versioni successive) non memorizza nella cache i piani risultanti da una richiesta di "piano stimato", sebbene visualizzerà comunque un piano delle funzioni ottimizzato per un NULLs valore del parametro al momento della compilazione.

Parametrizzazione semplice e forzata

Un'istruzione T-SQL ad hoc contenente valori letterali costanti può essere parametrizzata da SQL Server, perché la query è idonea per la parametrizzazione semplice o perché è abilitata l'opzione del database per la parametrizzazione forzata (oppure viene utilizzata una guida del piano con lo stesso effetto).

Un'istruzione parametrizzata in questo modo è anche soggetta allo sniffing dei parametri. La query seguente si qualifica per la parametrizzazione semplice:

SELECT 
    A.AddressLine1, 
    A.City, 
    A.PostalCode 
FROM Person.Address AS A 
WHERE 
    A.AddressLine1 = N'Heidestieg Straße 8664';

Il piano di esecuzione stimato mostra una stima di 2,5 righe in base al valore del parametro sniffato:

Infatti, la query restituisce 7 righe (la stima della cardinalità non è perfetta, anche quando i valori vengono sniffati):

A questo punto, potresti chiederti dove sono le prove che questa query è stata parametrizzata e il valore del parametro risultante è stato annusato. Esegui la query una seconda volta con un valore diverso:

SELECT 
    A.AddressLine1, 
    A.City, 
    A.PostalCode 
FROM Person.Address AS A 
WHERE 
    A.AddressLine1 = N'Winter der Böck 8550';

La query restituisce una riga:

Il piano di esecuzione mostra che la seconda esecuzione ha riutilizzato il piano parametrizzato che è stato compilato utilizzando un valore sniffato:

Parametrizzazione e sniffing sono attività separate

Un'istruzione ad hoc può essere parametrizzata da SQL Server senza i valori dei parametri vengono sniffati.

Per dimostrare, possiamo utilizzare il flag di traccia 4136 per disabilitare lo sniffing dei parametri per un batch che verrà parametrizzato dal server:

DBCC FREEPROCCACHE;
DBCC TRACEON (4136);
GO
SELECT
    A.AddressLine1, 
    A.City, 
    A.PostalCode 
FROM Person.Address AS A 
WHERE
    A.AddressLine1 = N'Heidestieg Straße 8664';
GO
SELECT 
    A.AddressLine1, 
    A.City, 
    A.PostalCode 
FROM Person.Address AS A 
WHERE 
    A.AddressLine1 = N'Winter der Böck 8550';
GO
DBCC TRACEOFF (4136);

Lo script genera istruzioni parametrizzate, ma il valore del parametro non viene annusato ai fini della stima della cardinalità. Per vedere questo, possiamo ispezionare la cache del piano:

WITH XMLNAMESPACES
    (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT
    DECP.cacheobjtype,
    DECP.objtype,
    DECP.usecounts,
    DECP.plan_handle,
    parameterized_plan_handle =
        DEQP.query_plan.value
        (
            '(//StmtSimple)[1]/@ParameterizedPlanHandle',
            'NVARCHAR(100)'
        )
FROM sys.dm_exec_cached_plans AS DECP
CROSS APPLY sys.dm_exec_sql_text(DECP.plan_handle) AS DEST
CROSS APPLY sys.dm_exec_query_plan(DECP.plan_handle) AS DEQP
WHERE 
    DEST.[text] LIKE N'%AddressLine1%'
    AND DEST.[text] NOT LIKE N'%XMLNAMESPACES%';

I risultati mostrano due voci della cache per le query ad hoc, collegate al piano di query parametrizzato (preparato) dall'handle del piano parametrizzato.

Il piano parametrizzato viene utilizzato due volte:

Il piano di esecuzione mostra una diversa stima della cardinalità ora che lo sniffing dei parametri è disabilitato:

Confronta la stima di 1,44571 righe con la stima di 2,5 righe utilizzata quando è stato abilitato lo sniffing dei parametri.

Con lo sniffing disabilitato, la stima deriva dalle informazioni sulla frequenza media relative a AddressLine1 colonna. Un estratto del DBCC SHOW_STATISTICS l'output per l'indice in questione mostra come è stato calcolato questo numero:moltiplicando il numero di righe nella tabella (19.614) per la densità (7,370826e-5) si ottiene la stima di 1,44571 riga.

Nota a margine: Si ritiene comunemente che solo i confronti di interi utilizzando un indice univoco possano qualificarsi per una semplice parametrizzazione. Ho scelto deliberatamente questo esempio (un confronto di stringhe che utilizza un indice non univoco) per confutarlo.

CON RECOMPILE e OPTION (RICIMPILA)

Quando si incontra un problema di sensibilità ai parametri, un consiglio comune sui forum e sui siti di domande e risposte è di "usare la ricompilazione" (supponendo che le altre opzioni di ottimizzazione presentate in precedenza non siano adatte). Sfortunatamente, questo consiglio viene spesso interpretato erroneamente nel senso di aggiungere WITH RECOMPILE opzione alla procedura memorizzata.

Usando WITH RECOMPILE ci riporta effettivamente al comportamento di SQL Server 2000, dove l'intera procedura memorizzata viene ricompilato ad ogni esecuzione.

Una alternativa migliore , su SQL Server 2005 e versioni successive, consiste nell'usare OPTION (RECOMPILE) suggerimento per la query solo sull'istruzione che soffre del problema dello sniffing dei parametri. Questo suggerimento per la query risulta in una ricompilazione dell'istruzione problematica solo. I piani di esecuzione per altre istruzioni all'interno della stored procedure vengono memorizzati nella cache e riutilizzati normalmente.

Usando WITH RECOMPILE significa anche che il piano compilato per la stored procedure non è memorizzato nella cache. Di conseguenza, non vengono mantenute informazioni sulle prestazioni in DMV come sys.dm_exec_query_stats .

L'utilizzo dell'hint di query significa invece che un piano compilato può essere memorizzato nella cache e le informazioni sulle prestazioni sono disponibili nei DMV (sebbene sia limitato all'esecuzione più recente, solo per l'istruzione interessata).

Per le istanze che eseguono almeno SQL Server 2008 build 2746 (Service Pack 1 con aggiornamento cumulativo 5), utilizzando OPTION (RECOMPILE) ha un altro vantaggio significativo su WITH RECOMPILE :Solo OPTION (RECOMPILE) abilita l'ottimizzazione dell'incorporamento dei parametri .

L'ottimizzazione dell'inclusione dei parametri

Lo sniffing dei valori dei parametri consente all'ottimizzatore di utilizzare il valore del parametro per derivare le stime della cardinalità. Entrambi WITH RECOMPILE e OPTION (RECOMPILE) generano piani di query con stime calcolate dai valori dei parametri effettivi su ciascuna esecuzione.

L'ottimizzazione dell'incorporamento dei parametri fa un ulteriore passo avanti in questo processo. I parametri della query vengono sostituiti con valori costanti letterali durante l'analisi della query.

Il parser è capace di semplificazioni sorprendentemente complesse e la successiva ottimizzazione delle query può perfezionare ulteriormente le cose. Considera la seguente procedura memorizzata, che presenta WITH RECOMPILE opzione:

CREATE PROCEDURE dbo.P
    @NameLike nvarchar(50),
    @Sort tinyint
WITH RECOMPILE
AS
BEGIN
    SELECT TOP (5)
        ProductID,
        Name
    FROM Production.Product
    WHERE
        @NameLike IS NULL
        OR Name LIKE @NameLike
    ORDER BY
        CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC,
        CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC,
        CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC,
        CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC;
END;

La procedura viene eseguita due volte, con i seguenti valori di parametro:

EXECUTE dbo.P
	@NameLike = N'K%',
	@Sort = 1;
GO
EXECUTE dbo.P
	@NameLike = N'[H-R]%',
	@Sort = 4;

Perché WITH RECOMPILE viene utilizzato, la procedura viene completamente ricompilata ad ogni esecuzione. I valori dei parametri vengono sniffati ogni volta e utilizzato dall'ottimizzatore per calcolare le stime di cardinalità.

Il piano per l'esecuzione della prima procedura è esattamente corretto, stimando 1 riga:

La seconda esecuzione stima 360 righe, molto vicine alle 366 viste in fase di esecuzione:

Entrambi i piani utilizzano la stessa strategia di esecuzione generale:scansiona tutte le righe in un indice, applicando il WHERE predicato della clausola come residuo; calcola il CASE espressione usata in ORDER BY clausola; ed esegui un Ordinamento Top N sul risultato del CASE espressione.

OPZIONE (RICIMPILA)

Ora ricrea la procedura memorizzata usando un OPTION (RECOMPILE) suggerimento di query invece di WITH RECOMPILE :

CREATE PROCEDURE dbo.P
    @NameLike nvarchar(50),
    @Sort tinyint
AS
BEGIN
    SELECT TOP (5)
        ProductID,
        Name
    FROM Production.Product
    WHERE
        @NameLike IS NULL
        OR Name LIKE @NameLike
    ORDER BY
        CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC,
        CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC,
        CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC,
        CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC
    OPTION (RECOMPILE);
END;

L'esecuzione della procedura memorizzata due volte con gli stessi valori di parametro di prima produce drammaticamente diverso piani di esecuzione.

Questo è il primo piano di esecuzione (con parametri che richiedono nomi che iniziano con “K”, ordinati per ProductID ascendente):

Il parser incorpora i valori dei parametri nel testo della query, risultando nella seguente forma intermedia:

SELECT TOP (5)
    ProductID,
    Name
FROM Production.Product
WHERE
    'K%' IS NULL
    OR Name LIKE 'K%'
ORDER BY
    CASE WHEN 1 = 1 THEN ProductID ELSE NULL END ASC,
    CASE WHEN 1 = 2 THEN ProductID ELSE NULL END DESC,
    CASE WHEN 1 = 3 THEN Name ELSE NULL END ASC,
    CASE WHEN 1 = 4 THEN Name ELSE NULL END DESC;

Il parser poi va oltre, rimuovendo le contraddizioni e valutando pienamente il CASE espressioni. Ciò si traduce in:

SELECT TOP (5)
    ProductID,
    Name
FROM Production.Product
WHERE
    Name LIKE 'K%'
ORDER BY
    ProductID ASC,
    NULL DESC,
    NULL ASC,
    NULL DESC;

Verrà visualizzato un messaggio di errore se si tenta di inviare la query direttamente a SQL Server, poiché l'ordinamento in base a un valore costante non è consentito. Tuttavia, questo è il modulo prodotto dal parser. È consentito internamente perché è sorto in seguito all'applicazione dell'ottimizzazione dell'incorporamento dei parametri . La query semplificata semplifica notevolmente la vita di Query Optimizer:

La Scansione indice cluster applica il LIKE predicato come residuo. Il calcolo scalare fornisce la costante NULLs valori. Il Top restituisce le prime 5 righe nell'ordine fornito dall'Indice cluster (evitando una sorta). In un mondo perfetto, Query Optimizer rimuoverebbe anche Compute Scalar che definisce i NULLs , poiché non vengono utilizzati durante l'esecuzione della query.

La seconda esecuzione segue esattamente lo stesso processo, risultando in un piano di query (per i nomi che iniziano con le lettere da "H" a "R", ordinato per Name discendente) in questo modo:

Questo piano prevede una Ricerca di indici non cluster che copre il LIKE range, un LIKE residuo predicato, la costante NULLs come prima, e un Top (5). Query Optimizer sceglie di eseguire un BACKWARD scansione dell'intervallo nella Ricerca dell'indice per evitare ancora una volta l'ordinamento.

Confronta il piano sopra con quello prodotto utilizzando WITH RECOMPILE , che non può utilizzare l'ottimizzazione dell'incorporamento dei parametri :

Questo esempio demo avrebbe potuto essere implementato meglio come una serie di IF istruzioni nella procedura (una per ogni combinazione di valori di parametro). Ciò potrebbe fornire vantaggi simili al piano di query, senza incorrere nella compilazione di una dichiarazione ogni volta. In scenari più complessi, la ricompilazione a livello di istruzione con l'incorporamento dei parametri fornito da OPTION (RECOMPILE) può essere una tecnica di ottimizzazione estremamente utile.

Una restrizione all'incorporamento

Esiste uno scenario in cui si utilizza OPTION (RECOMPILE) non comporterà l'applicazione dell'ottimizzazione dell'incorporamento dei parametri. Se l'istruzione assegna a una variabile, i valori dei parametri non vengono incorporati:

CREATE PROCEDURE dbo.P
    @NameLike nvarchar(50),
    @Sort tinyint
AS
BEGIN
    DECLARE
        @ProductID integer,
        @Name nvarchar(50);
 
    SELECT TOP (1)
        @ProductID = ProductID,
        @Name = Name
    FROM Production.Product
    WHERE
        @NameLike IS NULL
        OR Name LIKE @NameLike
    ORDER BY
        CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC,
        CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC,
        CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC,
        CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC
    OPTION (RECOMPILE);
END;

Perché il SELECT istruzione ora assegna a una variabile, i piani di query prodotti sono gli stessi di quando WITH RECOMPILE era usato. I valori dei parametri vengono ancora sniffati e utilizzati da Query Optimizer per la stima della cardinalità e OPTION (RECOMPILE) compila ancora solo la singola istruzione, solo il vantaggio dell'incorporamento dei parametri è perso.