Parametro Sniffing vs VARIABLES vs Recompile vs OPTIMIZE FOR UNKNOWN


40

Quindi stamattina abbiamo avuto un lungo processo che ha causato problemi (30 sec + tempo di esecuzione). Abbiamo deciso di verificare se la colpa fosse dello sniffing dei parametri. Quindi, riscriviamo il proc e impostiamo i parametri in entrata su variabili in modo da annullare lo sniffing dei parametri. Un approccio provato / vero. Bam, tempo di query migliorato (meno di 1 secondo). Quando si esamina il piano di query, i miglioramenti sono stati rilevati in un indice che l'originale non utilizzava.

Giusto per verificare che non abbiamo ottenuto un falso positivo, abbiamo fatto un programma gratuito dbcc sul proc originale e riprovato per vedere se i risultati migliorati sarebbero stati gli stessi. Ma, con nostra sorpresa, il proc originale funzionava ancora lentamente. Abbiamo provato di nuovo con un WITH RECOMPILE, ancora lento (abbiamo provato a ricompilare la chiamata al proc e all'interno del proc stesso). Abbiamo anche riavviato il server (ovviamente dev box).

Quindi, la mia domanda è questa ... come può essere la colpa dello sniffing dei parametri quando otteniamo la stessa query lenta su una cache del piano vuota ... non dovrebbero esserci parametri da snif ???

Siamo invece interessati dalle statistiche della tabella non correlate alla cache del piano. E se è così, perché impostare i parametri in entrata su variabili aiuta ??

In ulteriori test abbiamo anche scoperto che l'inserimento dell'OPTION (OPTIMIZE FOR UNKNOWN) negli interni del proc DID ottiene il piano migliorato previsto.

Quindi, alcuni di voi più intelligenti di me, potete darci qualche indizio su cosa sta succedendo dietro le quinte per produrre questo tipo di risultato?

In un'altra nota, anche il piano lento viene interrotto anticipatamente con la ragione, GoodEnoughPlanFoundmentre il piano veloce non ha alcuna ragione di interruzione anticipata nel piano reale.

In sintesi

  • Creazione di variabili dai parametri in entrata (1 secondo)
  • con ricompilazione (30+ sec)
  • dbcc freeproccache (30+ sec)
  • OPZIONE (OTTIMIZZARE PER UKNOWN) (1 sec)

AGGIORNARE:

Vedi il piano di esecuzione lenta qui: https://www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml

Vedi il piano di esecuzione veloce qui: https://www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml

Nota: tabella, schema, nomi degli oggetti modificati per motivi di sicurezza.

Risposte:


43

La domanda è

SELECT SUM(Amount) AS SummaryTotal
FROM   PDetail WITH(NOLOCK)
WHERE  ClientID = @merchid
       AND PostedDate BETWEEN @datebegin AND @dateend 

La tabella contiene 103.129.000 righe.

Il piano veloce cerca ClientId con un predicato residuo sulla data, ma deve recuperare 96 ricerche per recuperarlo Amount. La <ParameterList>sezione del piano è la seguente.

        <ParameterList>
          <ColumnReference Column="@dateend" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@datebegin" 
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@merchid" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

Il piano lento cerca per data e dispone di ricerche per valutare il predicato residuo su ClientId e recuperare l'importo (stimato 1 rispetto a 7.388.383 effettivi). La <ParameterList>sezione è

        <ParameterList>
          <ColumnReference Column="@EndDate" 
                           ParameterCompiledValue="'2013-02-01 23:59:00.000'" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@BeginDate" 
                           ParameterCompiledValue="'2013-01-01 00:00:00.000'"               
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@ClientID" 
                           ParameterCompiledValue="(78155)" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

In questo secondo caso il nonParameterCompiledValue è vuoto. SQL Server ha sniffato correttamente i valori utilizzati nella query.

Il libro "Risoluzione dei problemi pratici di SQL Server 2005" dice questo sull'uso delle variabili locali

L'uso di variabili locali per sconfiggere lo sniffing dei parametri è un trucco abbastanza comune, ma i suggerimenti OPTION (RECOMPILE)e OPTION (OPTIMIZE FOR)... sono generalmente soluzioni più eleganti e leggermente meno rischiose


Nota

In SQL Server 2005, la compilazione a livello di istruzione consente di rinviare la compilazione di una singola istruzione in una procedura memorizzata fino a poco prima della prima esecuzione della query. A quel punto il valore della variabile locale sarebbe noto. Teoricamente SQL Server potrebbe trarne vantaggio per annusare i valori delle variabili locali nello stesso modo in cui annusa i parametri. Tuttavia, poiché era comune utilizzare le variabili locali per annullare lo sniffing dei parametri in SQL Server 7.0 e SQL Server 2000+, lo sniffing delle variabili locali non era abilitato in SQL Server 2005. Potrebbe essere abilitato in una futura versione di SQL Server, sebbene sia un buon motivo per utilizzare una delle altre opzioni descritte in questo capitolo se hai una scelta.


Da un test rapido questo fine il comportamento descritto sopra è sempre lo stesso nel 2008 e nel 2012 e le variabili non vengono sniffate per la compilazione differita ma solo quando OPTION RECOMPILEviene utilizzato un suggerimento esplicito .

DECLARE @N INT = 0

CREATE TABLE #T ( I INT );

/*Reference to #T means this statement is subject to deferred compile*/
SELECT *
FROM   master..spt_values
WHERE  number = @N
       AND EXISTS(SELECT COUNT(*) FROM #T)

SELECT *
FROM   master..spt_values
WHERE  number = @N
OPTION (RECOMPILE)

DROP TABLE #T 

Nonostante la compilazione differita, la variabile non viene sniffata e il conteggio delle righe stimato non è preciso

Stime vs effettive

Quindi presumo che il piano lento si riferisca a una versione con parametri della query.

Il ParameterCompiledValueè uguale ParameterRuntimeValueper tutti i parametri quindi questo non è tipico parametro sniffing (dove il piano è stato compilato per un insieme di valori quindi eseguire un altro insieme di valori).

Il problema è che il piano che viene compilato per i valori dei parametri corretti è inappropriato.

Probabilmente stai colpendo il problema con le date ascendenti descritte qui e qui . Per una tabella con 100 milioni di righe è necessario inserire (o modificare in altro modo) 20 milioni prima che SQL Server aggiorni automaticamente le statistiche. Sembra che l'ultima volta che sono state aggiornate zero righe corrispondessero all'intervallo di date nella query, ma ora 7 milioni lo fanno.

È possibile pianificare aggiornamenti delle statistiche più frequenti, prendere in considerazione i flag di traccia 2389 - 90o utilizzarli in OPTIMIZE FOR UKNOWNmodo da ricadere su ipotesi piuttosto che essere in grado di utilizzare le statistiche attualmente fuorvianti nella datetimecolonna.

Ciò potrebbe non essere necessario nella prossima versione di SQL Server (dopo il 2012). Un elemento Connect correlato contiene la risposta intrigante

Inserito da Microsoft il 28/08/2012 alle 13:35
Abbiamo apportato un miglioramento alla stima della cardinalità per la prossima versione principale che risolve sostanzialmente questo problema. Resta sintonizzato per i dettagli una volta pubblicate le anteprime. Eric

Questo miglioramento del 2014 viene esaminato da Benjamin Nevarez verso la fine dell'articolo:

Un primo sguardo al nuovo stimatore della cardinalità di SQL Server .

Sembra che il nuovo stimatore della cardinalità ricadrà e utilizzerà la densità media in questo caso anziché fornire la stima a 1 riga.

Alcuni dettagli aggiuntivi sullo stimatore della cardinalità 2014 e il problema chiave crescente qui:

Nuove funzionalità in SQL Server 2014 - Parte 2 - Nuova stima della cardinalità


29

Quindi, la mia domanda è questa ... come può essere la colpa dello sniffing dei parametri quando otteniamo la stessa query lenta su una cache del piano vuota ... non dovrebbero esserci parametri da annusare?

Quando SQL Server compila una query contenente valori di parametro, annusa i valori specifici di tali parametri per la stima della cardinalità (conteggio delle righe). Nel tuo caso, i particolari valori di @BeginDate, @EndDatee @ClientIDvengono utilizzati quando si sceglie un piano di esecuzione. Puoi trovare maggiori dettagli sullo sniffing dei parametri qui e qui . Sto fornendo questi collegamenti di base perché la domanda sopra mi fa pensare che il concetto sia attualmente capito in modo imperfetto - ci sono sempre valori dei parametri da annusare quando viene compilato un piano.

Ad ogni modo, questo è tutto oltre il punto, perché lo sniffing dei parametri non è il problema qui, come ha sottolineato Martin Smith. Al momento della compilazione della query lenta, le statistiche indicavano che non c'erano righe per i valori sniffati di @BeginDatee @EndDate:

Il piano lento ha annusato i valori

I valori sniffati sono molto recenti, il che suggerisce il problema chiave crescente menzionato da Martin. Poiché si stima che l'indice cerca nelle date restituisca solo una singola riga, l'ottimizzatore sceglie ClientIDun residuo che spinge il predicato sull'operatore Ricerca chiave come residuo.

La stima a riga singola è anche il motivo per cui l'ottimizzatore smette di cercare piani migliori, restituendo un messaggio "Abbastanza piano trovato". Il costo totale stimato del piano lento con la stima a riga singola è solo 0,013136 unità di costo, quindi non ha senso cercare di trovare qualcosa di meglio. Tranne, ovviamente, la ricerca in realtà restituisce 7.388.383 righe anziché una, causando lo stesso numero di ricerche chiave.

Le statistiche possono essere difficili da mantenere aggiornate e utili su tabelle di grandi dimensioni e il partizionamento presenta sfide proprie al riguardo. Non ho avuto particolare successo anche io con i flag di traccia 2389 e 2390, ma siete invitati a provarli. Le build più recenti di SQL Server (R2 SP1 e successive) dispongono di aggiornamenti statistici dinamici disponibili, ma questi aggiornamenti statistici per partizione non sono ancora implementati. Nel frattempo, potresti voler pianificare un aggiornamento manuale delle statistiche ogni volta che apporti modifiche significative a questa tabella.

Per questa particolare query, penserei di implementare l'indice suggerito dall'ottimizzatore durante la compilazione del piano di query veloce:

/*
The Query Processor estimates that implementing the following index could improve
the query cost by 98.8091%.

WARNING: This is only an estimate, and the Query Processor is making this 
recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide impact,
including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [dbo].[PDetail] ([ClientID],[PostedDate])
INCLUDE ([Amount]);

L'indice dovrebbe essere allineato alle partizioni, con una ON PartitionSchemeName (PostedDate)clausola, ma il punto è che fornire un percorso di accesso ai dati ovviamente migliore aiuterà l'ottimizzatore a evitare scelte di piano sbagliate, senza ricorrere a OPTIMIZE FOR UNKNOWNsuggerimenti o soluzioni alternative all'antica come l'utilizzo di variabili locali.

Con l'indice migliorato, la Ricerca chiave per recuperare la Amountcolonna verrà eliminata, il processore di query può ancora eseguire l'eliminazione della partizione dinamica e utilizzare una ricerca per trovare il particolare ClientIDe l'intervallo di date.


Vorrei poter contrassegnare due risposte come corrette, ma ancora una volta, grazie per le informazioni aggiuntive - molto istruttive.
RThomas,

1
Sono passati un paio d'anni da quando l'ho pubblicato ... ma volevo solo farti sapere. Uso sempre il termine "capito in modo imperfetto" per tutto il tempo, e penso sempre a Paul White quando lo faccio. Mi fa ridere ogni volta.
RThomas,

0

Ho avuto lo stesso identico problema in cui una procedura memorizzata è diventata lenta OPTIMIZE FOR UNKNOWNe RECOMPILEsuggerimenti di query hanno risolto la lentezza e accelerato i tempi di esecuzione. Tuttavia, i seguenti due metodi non hanno influenzato la lentezza della procedura memorizzata: (i) Svuotare la cache (ii) utilizzando WITH RECOMPILE. Quindi, proprio come hai detto, in realtà non si trattava di sniffare i parametri.

Anche le bandiere di traccia 2389 e 2390 non hanno aiutato. Ho appena aggiornato le statistiche ( EXEC sp_updatestats) per me.

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.