OPTION (RECOMPILE) è sempre più veloce; Perché?


169

Ho riscontrato una situazione strana in cui l'aggiunta OPTION (RECOMPILE)alla mia query provoca l'esecuzione in mezzo secondo, mentre la sua omissione fa sì che la query impieghi più di cinque minuti.

Questo è il caso in cui la query viene eseguita da Query Analyzer o dal mio programma C # tramite SqlCommand.ExecuteReader(). Chiamare (o non chiamare) DBCC FREEPROCCACHEo DBCC dropcleanbuffersnon fa differenza; I risultati delle query vengono sempre restituiti istantaneamente con OPTION (RECOMPILE)e per più di cinque minuti senza di essa. La query viene sempre chiamata con gli stessi parametri [per il bene di questo test].

Sto usando SQL Server 2008.

Sono abbastanza a mio agio con la scrittura di SQL ma non ho mai usato un OPTIONcomando in una query prima e non avevo familiarità con l'intero concetto di cache del piano fino alla scansione dei post su questo forum. La mia comprensione dai post è che OPTION (RECOMPILE)è un'operazione costosa. Apparentemente crea una nuova strategia di ricerca per la query. Allora perché è che le query successive che omettono OPTION (RECOMPILE)sono così lente? Le query successive non dovrebbero utilizzare la strategia di ricerca calcolata nella chiamata precedente che includeva il suggerimento per la ricompilazione?

È molto insolito avere una query che richiede un suggerimento di ricompilazione per ogni singola chiamata?

Ci scusiamo per la domanda entry-level ma non riesco davvero a capirlo.

AGGIORNAMENTO: mi è stato chiesto di pubblicare la query ...

select acctNo,min(date) earliestDate 
from( 
    select acctNo,tradeDate as date 
    from datafeed_trans 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_money 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_jnl 
    where feedid=@feedID and feedDate=@feedDate 
)t1 
group by t1.acctNo
OPTION(RECOMPILE)

Quando eseguo il test da Query Analyzer, preposto le seguenti righe:

declare @feedID int
select @feedID=20

declare @feedDate datetime
select @feedDate='1/2/2009'

Quando lo chiamo dal mio programma C #, i parametri vengono passati tramite la SqlCommand.Parametersproprietà.

Ai fini di questa discussione, puoi presumere che i parametri non cambino mai, quindi possiamo escludere l'odore dei parametri non ottimali come causa.


3
Quali sono i parametri della query? Dai un'occhiata a questo articolo. blogs.msdn.com/b/turgays/archive/2013/09/10/… Fondamentalmente, SQL tenta di generare il piano di query in base ai parametri quando il proc viene compilato per la prima volta. Potrebbe generare un piano non ottimale quando inizi a passare parametri diversi, forse più realistici
Sparky

3
La query è abbastanza concisa per essere elencata qui? Penso che Sparky sia corretto ed è probabilmente correlato allo sniffing dei parametri, ho avuto un problema simile che mi ha confuso fino a leggere questo eccellente articolo: sommarskog.se/query-plan-mysteries.html
Chris

1
Ma in questo caso (per il bene di questo test) passo sempre con gli stessi parametri. Nessun'altra app è stata in grado di intrufolarsi e chiamare la query utilizzando altri parametri. Grazie per gli articoli Revisioneremo.
Chad Decker,

2
Ciò può accadere perché annusa i valori dei parametri e delle variabili o perché semplifica notevolmente. Esempi di semplificazioni maggiori sarebbero al collasso X = @X OR @X IS NULLper X=@Xe l'esecuzione di un cercano Vedere qui o spingendo predicati ulteriormente verso il basso su una vista con le funzioni delle finestre
Martin Smith

3
Dopo la modifica, l'esempio di Query Analyzer utilizza variabili, non parametri. il valore di quelli non viene mai annusato se non con RECOMPILE. In ogni caso, catturare i piani di esecuzione e osservare le differenze.
Martin Smith,

Risposte:


157

Ci sono momenti in cui l'uso OPTION(RECOMPILE)ha senso. Nella mia esperienza, l'unica volta che questa è un'opzione praticabile è quando si utilizza SQL dinamico. Prima di scoprire se questo ha senso nella tua situazione, ti consiglio di ricostruire le tue statistiche. Questo può essere fatto eseguendo quanto segue:

EXEC sp_updatestats

E poi ricreare il tuo piano di esecuzione. Ciò garantirà che quando viene creato il piano di esecuzione utilizzerà le informazioni più recenti.

L'aggiunta OPTION(RECOMPILE)ricostruisce il piano di esecuzione ogni volta che viene eseguita la query. Non l'ho mai sentito descritto come, creates a new lookup strategyma forse stiamo usando solo termini diversi per la stessa cosa.

Quando viene creata una procedura memorizzata (sospetto che si stia chiamando sql ad hoc da .NET ma se si utilizza una query con parametri, questa finisce per essere una chiamata proc memorizzata ) SQL Server tenta di determinare il piano di esecuzione più efficace per questa query in base ai dati nel database e ai parametri passati ( parametro sniffing ), quindi memorizza nella cache questo piano. Ciò significa che se si crea la query in cui sono presenti 10 record nel database e quindi si esegue quando ci sono 100.000.000 di record, il piano di esecuzione memorizzato nella cache potrebbe non essere più efficace.

In sintesi: non vedo alcun motivo che OPTION(RECOMPILE)potrebbe essere un vantaggio qui. Ho il sospetto che devi solo aggiornare le tue statistiche e il tuo piano di esecuzione. La ricostruzione delle statistiche può essere una parte essenziale del lavoro DBA a seconda della situazione. Se i problemi persistono anche dopo aver aggiornato le statistiche, suggerirei di pubblicare entrambi i piani di esecuzione.

E per rispondere alla tua domanda: sì, direi che è molto insolito che la tua migliore opzione sia quella di ricompilare il piano di esecuzione ogni volta che esegui la query.


22
Sì, sp_updatestats ha fatto il trucco. Hai colpito il chiodo in testa quando hai menzionato una query inizialmente eseguita su una tabella con 10 record e ora la tabella ha milioni di record. Quello era esattamente il mio caso. Non l'ho menzionato nel post perché non pensavo che avesse importanza. Roba affascinante. Grazie ancora.
Chad Decker,

3
È l'unico modo in cui ho scoperto di lavorare con le variabili di tabella, perché SQL pensa sempre che ci sia una singola riga dentro. Quando contiene diverse migliaia di righe diventa un problema.
Alex Zhukovskiy,

4
Un dettaglio interessante: l'aggiornamento delle statistiche invalida implicitamente tutti i piani memorizzati nella cache che utilizzano queste statistiche, ma solo se le statistiche sono effettivamente cambiate dopo l'azione di aggiornamento . Quindi, per le tabelle di sola lettura molto inclinate, sembra che un esplicito OPTION (RECOMPILE)potrebbe essere l'unica soluzione.
Groo,

141

Spesso quando c'è una differenza drastica da una corsa all'altra di una query trovo che si tratti spesso di uno dei 5 problemi.

  1. STATISTICHE- Le statistiche non sono aggiornate. Un database memorizza le statistiche sull'intervallo e la distribuzione dei tipi di valori in varie colonne su tabelle e indici. Questo aiuta il motore di query a sviluppare un "Piano" di attacco per come eseguirà la query, ad esempio il tipo di metodo che utilizzerà per abbinare le chiavi tra le tabelle usando un hash o guardando attraverso l'intero set. È possibile chiamare Update Statistics sull'intero database o solo su determinate tabelle o indici. Questo rallenta la query da una corsa all'altra perché quando le statistiche non sono aggiornate, è probabile che il piano di query non sia ottimale per i dati appena inseriti o modificati per la stessa query (spiegati più avanti in seguito). Potrebbe non essere corretto aggiornare immediatamente le statistiche su un database di produzione poiché potrebbero verificarsi sovraccarico, rallentamento e ritardo a seconda della quantità di dati da campionare. Puoi anche scegliere di utilizzare una scansione completa o campionamento per aggiornare le statistiche. Se si esamina il Piano delle query, è quindi possibile visualizzare anche le statistiche sugli Indici in uso utilizzando tale comandoDBCC SHOW_STATISTICS (nome tab, nome indice) . Questo ti mostrerà la distribuzione e gli intervalli delle chiavi che il piano di query sta usando per basare il suo approccio.

  2. PARAMETER SNIFFING - Il piano di query memorizzato nella cache non è ottimale per i parametri specifici che si stanno passando, anche se la query stessa non è cambiata. Ad esempio, se si passa a un parametro che recupera solo 10 su 1.000.000 di righe, il piano di query creato può utilizzare un hash join, tuttavia se il parametro che si passa utilizzerà 750.000 di 1.000.000 di righe, il piano creato potrebbe essere un scansione indice o scansione tabella. In una situazione del genere è possibile indicare all'istruzione SQL di utilizzare l'opzione OPTION (RECOMPILE) o un SP da utilizzare WITH RECOMPILE. Per dire al motore questo è un "piano monouso" e non usare un piano memorizzato nella cache che probabilmente non si applica. Non esiste una regola su come prendere questa decisione, dipende dalla conoscenza del modo in cui la query verrà utilizzata dagli utenti.

  3. INDICI - È possibile che la query non sia cambiata, ma una modifica altrove come la rimozione di un indice molto utile ha rallentato la query.

  4. ROWS CHANGED - Le righe su cui si esegue la query cambiano drasticamente da una chiamata all'altra. Di solito le statistiche vengono automaticamente aggiornate in questi casi. Tuttavia, se si crea SQL dinamico o si chiama SQL in un ciclo stretto, è possibile che si stia utilizzando un piano di query obsoleto basato sul numero drastico errato di righe o statistiche. Anche in questo caso OPTION (RECOMPILE) è utile.

  5. LA LOGICA È la logica, la tua query non è più efficiente, andava bene per un piccolo numero di righe, ma non è più scalabile. Ciò di solito comporta un'analisi più approfondita del piano di query. Ad esempio, non puoi più fare le cose alla rinfusa, ma devi Chunk cose e fare piccoli commit, o il tuo Prodotto incrociato andava bene per un set più piccolo ma ora occupa CPU e memoria man mano che si ingrandisce, questo può anche essere vero per usando DISTINCT, stai chiamando una funzione per ogni riga, le tue corrispondenze di tasti non usano un indice a causa della conversione del tipo CASTING o dei NULL o delle funzioni ... Troppe possibilità qui.

In generale, quando scrivi una query, dovresti avere un'idea mentale di come vengono distribuiti determinati dati all'interno della tabella. Una colonna, ad esempio, può avere un numero uniformemente distribuito di valori diversi, oppure può essere distorta, l'80% delle volte ha un insieme specifico di valori, indipendentemente dal fatto che la distribuzione varierà frequentemente nel tempo o sia abbastanza statica. Questo ti darà un'idea migliore di come creare una query efficiente. Ma anche quando il debug delle prestazioni della query ha una base per costruire un'ipotesi sul perché sia ​​lenta o inefficiente.


2
grazie amico. Questa è un'informazione eccellente. Non sarei stato in grado di capire la tua risposta quando inizialmente ho pubblicato la mia domanda, ma ora ha perfettamente senso per me.
Chad Decker,

3
PARAMETER SNIFFING è di gran lunga la più grande rovina per la mia esistenza. Non sapevo nemmeno di questo comando fino a quando un'intervista fallita. La mia soluzione allo sniffing dei parametri è sempre stata l'hash dei valori dei parametri e aggiungere "AND {hash} = {hash}" in modo che sql fosse sempre diverso per valori diversi. Un trucco, ma ha funzionato.
Jeremy Boyd,

27

Per aggiungere all'eccellente elenco (fornito da @CodeCowboyOrg) delle situazioni in cui OPTION (RECOMPILE) può essere molto utile,

  1. Variabili di tabella . Quando si utilizzano le variabili di tabella, non vi saranno statistiche predefinite per la variabile di tabella, che spesso portano a grandi differenze tra le righe stimate e quelle effettive nel piano di query. L'uso di OPTION (RECOMPILE) su query con variabili di tabella consente la generazione di un piano di query con una stima molto migliore dei numeri di riga coinvolti. Ho avuto un uso particolarmente critico di una variabile di tabella che era inutilizzabile e che avrei abbandonato, fino a quando ho aggiunto OPTION (RECOMPILE). Il tempo di esecuzione è andato dalle ore a pochi minuti. Questo è probabilmente insolito, ma in ogni caso, se stai usando le variabili di tabella e stai lavorando sull'ottimizzazione, vale la pena vedere se OPTION (RECOMPILE) fa la differenza.

1
Ho una query con 5 variabili di tabella. Sulla mia macchina si esegue per più di mezz'ora. Sulla macchina del mio collega viene eseguito in <1 secondo. Le macchine hanno hardware simile e la stessa versione di SQL Server. Se entrambi aggiungiamo OPTION (RECOMPILE), viene eseguito in 2 secondi su entrambe le macchine. In tutti i casi il test di esecuzione viene eseguito in SSMS. Cosa potrebbe causare questa differenza?
Adam,

1
Puoi confrontare il piano di esecuzione per esso sul tuo computer e sul computer dei tuoi colleghi senza Opzione (ricompilare)? Ciò potrebbe mostrare l'origine della differenza.
DWright

1
per le tabelle temporanee, è una stessa situazione?
Muflix,

1
@muflix: bella domanda. Non credo che l'effetto sia lo stesso per le tabelle temporanee, dal momento che hanno statistiche e il motore dovrebbe fare scelte di ricompilazione automatica come per altre tabelle, credo (ma non ne sono certo). Forse qualcun altro lo sa con maggiore certezza.
Lasciare il

2
Le statistiche nelle tabelle temporanee non vengono aggiornate o ricompilate automaticamente, quindi il programmatore deve farlo.
J. Michael Wuerth,

1

Le primissime azioni prima di sintonizzare le query è di deframmentare / ricostruire gli indici e le statistiche, altrimenti stai perdendo tempo.

È necessario controllare il piano di esecuzione per vedere se è stabile (è lo stesso quando si cambiano i parametri), in caso contrario, potrebbe essere necessario creare un indice di copertura (in questo caso per ogni tabella) (sapendo che è possibile crearne uno che è utile anche per altre query).

come esempio: crea indice idx01_datafeed_trans Su datafeed_trans (feedid, feedDate) INCLUDE (acctNo, tradeDate)

se il piano è stabile o è possibile stabilizzarlo, è possibile eseguire la frase con sp_executesql ('frase sql') per salvare e utilizzare un piano di esecuzione fisso.

se il piano è instabile, è necessario utilizzare un'istruzione ad-hoc o EXEC ('frase sql') per valutare e creare un piano di esecuzione ogni volta. (o una procedura memorizzata "con ricompilazione").

Spero che sia d'aiuto.


1

Necroing questa domanda ma c'è una spiegazione che nessuno sembra aver preso in considerazione.

STATISTICHE - Le statistiche non sono disponibili o fuorvianti

Se sono vere tutte le seguenti condizioni:

  1. È probabile che le colonne feedid e feedDate siano altamente correlate (ad esempio, un ID feed è più specifico di una data feed e il parametro date è informazioni ridondanti).
  2. Non esiste un indice con entrambe le colonne come colonne sequenziali.
  3. Non ci sono statistiche create manualmente che coprono entrambe queste colonne.

Quindi il server sql potrebbe assumere erroneamente che le colonne non siano correlate, portando a stime di cardinalità inferiori alle attese per l'applicazione di entrambe le restrizioni e la selezione di un piano di esecuzione scadente. La correzione in questo caso sarebbe quella di creare un oggetto statistico che collega le due colonne, il che non è un'operazione costosa.

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.