Bug delle prestazioni dell'indice datetime di SQL Server 2008


11

Stiamo utilizzando SQL Server 2008 R2 e disponiamo di una tabella (100 M + righe) molto grande con un indice ID primario e una datetimecolonna con un indice non cluster. Stiamo riscontrando alcuni comportamenti client / server molto insoliti basati sull'uso di una order byclausola specificamente su una colonna di data / ora indicizzata .

Ho letto il seguente post: /programming/1716798/sql-server-2008-ordering-by-datetime-is-too-slow ma c'è molto di più nel client / server di quello che è iniziare descritto qui.

Se eseguiamo la seguente query (modificata per proteggere alcuni contenuti):

select * 
from [big table] 
where serial_number = [some number] 
order by test_date desc

La query scade ogni volta. In SQL Server Profiler la query eseguita appare così al server:

exec sp_cursorprepexec @p1 output,@p2 output,NULL,N'select * .....

Ora se modifichi la query in, dì questo:

declare @temp int;
select * from [big table] 
where serial_number = [some number] 
order by test_date desc

SQL Server Profiler mostra che la query eseguita è simile al server e FUNZIONA all'istante:

exec sp_prepexec @p1 output, NULL, N'declare @temp int;select * from .....

È un dato di fatto, puoi anche inserire un commento vuoto ('-;') invece di un'istruzione dichiarare inutilizzata e ottenere lo stesso risultato. Quindi inizialmente stavamo puntando al pre-processore sp come causa principale di questo problema, ma se lo fai:

select * 
from [big table] 
where serial_number = [some number] 
order by Cast(test_date as smalldatetime) desc

Funziona anche all'istante (puoi lanciarlo come qualsiasi altro datetimetipo), restituendo il risultato in millisecondi. E il profiler mostra la richiesta al server come:

exec sp_cursorprepexec @p1 output, @p2 output, NULL, N'select * from .....

In tal modo si esclude in qualche modo la sp_cursorprepexecprocedura dalla piena causa del problema. Aggiungete a ciò il fatto che sp_cursorprepexecviene anche chiamato quando non viene utilizzato 'ordina per' e il risultato viene restituito istantaneamente.

Abbiamo cercato su Google per questo problema un bel po ', e vedo problemi simili pubblicati da altri, ma nessuno che lo divide a questo livello.

Quindi altri hanno assistito a questo comportamento? Qualcuno ha una soluzione migliore del mettere SQL senza senso davanti all'istruzione select per cambiare il comportamento? Dato che SQL Server dovrebbe invocare l'ordine dopo che i dati sono stati raccolti, sembra che si tratti di un bug nel server che persiste da molto tempo. Abbiamo riscontrato che questo comportamento è coerente su molte delle nostre tabelle di grandi dimensioni ed è riproducibile.

modifiche:

Dovrei anche aggiungere un inserimento per far forceseekscomparire il problema.

Dovrei aggiungere per aiutare i ricercatori, l'errore di timeout ODBC generato è: [Microsoft] [Driver ODBC SQL Server] Operazione annullata

Aggiunto il 10/12/2012: Continuo a cercare la causa principale (oltre a aver creato un campione da dare a Microsoft, inserirò qui tutti i risultati dopo averlo inviato). Ho scavato nel file di traccia ODBC tra una query funzionante (con un commento aggiunto / dichiarazione dichiarazione) e una query non funzionante. La differenza di traccia fondamentale è pubblicata di seguito. Si verifica sulla chiamata alla chiamata SQLExtendedFetch al termine di tutte le discussioni di SQLBindCol. La chiamata ha esito negativo con il codice di ritorno -1 e il thread padre immette quindi SQLCancel. Dato che siamo in grado di produrlo con entrambi i driver ODBC Native Client e Legacy, sto ancora indicando un problema di compatibilità sul lato server.

(clip)
MSSQLODBCTester 1664-1718   EXIT  SQLBindCol  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10
        UWORD                       16 
        SWORD                        1 <SQL_C_CHAR>
        PTR                0x03259030
        SQLLEN                    51
        SQLLEN *            0x0326B820 (0)

MSSQLODBCTester 1664-1718   ENTER SQLExtendedFetch 
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

MSSQLODBCTester 1664-1fd0   ENTER SQLCancel 
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   EXIT  SQLExtendedFetch  with return code -1 (SQL_ERROR)
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

        DIAG [S1008] [Microsoft][ODBC SQL Server Driver]Operation canceled (0) 

MSSQLODBCTester 1664-1fd0   EXIT  SQLCancel  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 0 (SQL_SUCCESS)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C [       5] "S1008"
        SDWORD *            0x08BFFF08 (0)
        WCHAR *             0x08BFF85C [      53] "[Microsoft][ODBC SQL Server Driver]Operation canceled"
        SWORD                      511 
        SWORD *             0x08BFFEE6 (53)

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 100 (SQL_NO_DATA_FOUND)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6
(clip)

Aggiunto un caso Microsoft Connect del 10/12/2012:

https://connect.microsoft.com/SQLServer/feedback/details/767196/order-by-datetime-in-odbc-fails-for-clean-sql-statements#details

Dovrei anche notare che abbiamo cercato i piani di query sia per le query funzionanti che non funzionanti. Entrambi vengono riutilizzati in modo appropriato in base al conteggio delle esecuzioni. Lo svuotamento dei piani memorizzati nella cache e la riesecuzione non modificano il successo della query.


Cosa succede se ci provi select id, test_date from [big table] where serial_number = ..... order by test_date- mi chiedo solo se ciò SELECT *abbia un impatto negativo sulla tua performance. Se hai un indice non cluster attivato test_datee un indice cluster attivato id(supponendo che sia quello che viene chiamato), questa query dovrebbe essere coperta da quell'indice non cluster e quindi dovrebbe restituire abbastanza rapidamente
marc_s

Scusa, buon punto. Avrei dovuto includere che abbiamo provato a modificare lo spazio di colonna selezionato (rimuovendo '*', ecc.) Pesantemente con varie combinazioni. Il comportamento sopra descritto è persistito attraverso tali cambiamenti.
DBtheDBA,

Ho collegato i miei account ora a quel sito. Se un moderatore vuole spostare il post su quel sito, sto bene comunque. Uno dei miei sviluppatori mi ha segnalato quel sito dopo che ho pubblicato qui.
DBtheDBA,

Quale stack client viene utilizzato qui? Senza l'intero testo di traccia, questo sembra il problema. Prova a racchiudere la chiamata originale all'interno sp_executesqle guarda cosa succede.
Jon Seigel,

1
Che aspetto ha il piano di esecuzione lenta? Il parametro annusa?
Martin Smith,

Risposte:


6

Non c'è mistero, ottieni un buon (ohm) o (davvero) cattivo piano praticamente casuale perché non c'è una scelta netta per l'indice da usare. Sebbene sia convincente per la clausola ORDER BY e quindi eviti l'ordinamento, l'indice non cluster sulla colonna datetime è una scelta molto scadente per questa query. Ciò che renderebbe un indice molto migliore per questa query sarebbe uno (serial_number, test_date). Ancora meglio, questo sarebbe un ottimo candidato per una chiave di indice cluster .

Come regola generale, le serie temporali dovrebbero essere raggruppate per colonna temporale, poiché la stragrande maggioranza delle richieste è interessata a intervalli di tempo specifici. Se anche i dati sono intrinsecamente partizionati su una colonna con bassa selettività, come nel caso del numero seriale, questa colonna deve essere aggiunta come quella più a sinistra nella definizione della chiave cluster.


Sono un po 'confuso qui. Perché il piano dovrebbe essere basato sulla the orderclausola? Il piano non dovrebbe limitarsi alle wherecondizioni poiché l'ordine dovrebbe avvenire solo dopo che le righe sono state recuperate? Perché il server dovrebbe provare a ordinare i record prima di avere l'intero set di risultati?
DBtheDBA,

5
Questo non spiega anche perché l'aggiunta di un commento all'inizio della query influisca sulla durata della corsa.
cfradenburg,

Inoltre, le nostre tabelle sono quasi sempre interrogate dal numero di serie, non da test_date. Abbiamo indici non cluster su entrambi e un cluster solo sulla colonna id nella tabella. È un archivio di dati operativi e l'aggiunta di indici cluster su altre colonne determinerebbe solo la divisione delle pagine e prestazioni peggiori.
DBtheDBA,

1
@DBtheDBA: se vuoi presentare un reclamo per un 'bug' devi fare un'indagine e una divulgazione adeguate. Lo schema esatto della tua tabella e le statistiche esportate, segui Come generare uno script dei metadati del database necessari per creare un database solo statistico in SQL Server 2005 e in SQL Server 2008 , in particolare tutte le principali statistiche degli script : statistiche degli script e istogrammi . Aggiungi questi alle informazioni sul post insieme ai passaggi che riproducono il problema.
Remus Rusanu,

1
L'abbiamo letto prima durante le nostre ricerche, e capisco cosa stai dicendo, ma c'è un difetto fondamentale in qualcosa che il server sta facendo qui. Abbiamo ricostruito la tabella e gli indici e l'abbiamo riprodotta su una nuova tabella. L'opzione di ricompilazione non risolve il problema, il che è un grande suggerimento che qualcosa non va. Non ho dubbi sul fatto che mettere indici cluster su tutto ciò possa potenzialmente risolvere questo problema, ma non è una soluzione alla causa principale, è una soluzione alternativa e costosa su una tabella di grandi dimensioni.
DBtheDBA,

0

Documentare i dettagli su come riprodurre il bug e inviarlo su connect.microsoft.com. Ho controllato e non riuscivo a vedere nulla là fuori che sarebbe correlato a questo.


Domani chiederò al mio DBA di scrivere uno script per creare un ambiente da riprodurre. Non penso sia così difficile. Lo pubblicherò anche qui, nel caso qualcuno fosse interessato a provarlo da solo.
DBtheDBA,

Pubblica anche l'elemento di connessione quando viene aperto. In questo modo, se qualcun altro ha questo problema, viene indicato direttamente. E chiunque stia osservando questa domanda potrebbe voler votare l'elemento in modo che Microsoft abbia maggiori probabilità di prestargli attenzione.
cfradenburg,

0

La mia ipotesi è che si stia eseguendo la cache del piano di query. (Remus potrebbe dire la stessa cosa di me, ma in un modo diverso.)

Ecco un sacco di dettagli su come SQL pianifica la memorizzazione nella cache .

Ripassando i dettagli: qualcuno ha eseguito quella query in precedenza, per un particolare [un numero]. SQL ha esaminato il valore fornito, gli indici e le statistiche per la tabella / le colonne pertinenti, ecc. E ha creato un piano che ha funzionato bene per quel particolare [qualche numero]. Quindi ha memorizzato nella cache il piano, eseguito e restituito i risultati al chiamante.

Successivamente, qualcun altro sta eseguendo la stessa query, per un valore diverso di [un certo numero]. Questo particolare valore determina un numero molto diverso di righe di risultati e il motore dovrebbe creare un piano diverso per questa istanza della query. Ma non funziona così. Invece, SQL accetta la query e (più o meno) esegue una ricerca con distinzione tra maiuscole e minuscole della cache delle query, cercando una versione preesistente della query. Quando trova quello precedente, usa solo quel piano.

L'idea è che risparmi il tempo necessario per decidere il piano e costruirlo. Il buco nell'idea è quando la stessa query viene eseguita con valori che producono risultati selvaggiamente diversi. Dovrebbero avere piani diversi, ma non lo fanno. Chiunque abbia eseguito prima la query aiuta a impostare il comportamento per tutti coloro che la eseguono in seguito.

Un breve esempio: selezionare * da [persone] dove cognome = 'SMITH' - cognome molto popolare negli Stati Uniti GO selezionare * da [persone] dove cognome = 'BONAPARTE' - NON popolare cognome negli Stati Uniti

Quando viene eseguita la query per BONAPARTE, il piano creato per SMITH verrà riutilizzato. Se SMITH ha causato una scansione della tabella (che potrebbe essere buona , se le righe nella tabella sono 99% SMITH), allora BONAPARTE otterrà anche una scansione della tabella. Se BONAPARTE è stato eseguito prima di SMITH, un piano che utilizza un indice potrebbe essere creato e utilizzato, quindi riutilizzato per SMITH (che potrebbe essere migliore con la scansione della tabella). Le persone potrebbero non notare che le prestazioni per SMITH sono scarse poiché si aspettano prestazioni scadenti poiché l'intera tabella deve essere letta e letta l'indice e il passaggio alla tabella non viene notato direttamente.

Per quanto riguarda le modifiche che dovrebbero cambiare, sospetto che SQL lo stia vedendo come una domanda completamente diversa e stia costruendo un nuovo piano, specifico per il valore di [qualche numero].

Per verificare ciò, apportare una modifica senza senso alla query, come aggiungere alcuni spazi tra FOR e il nome della tabella o inserire un commento alla fine. È veloce? In tal caso, è perché quella query è leggermente diversa da quella presente nella cache, quindi SQL ha fatto quello che fa per le "nuove" query.

Per una soluzione, guarderei tre cose. Innanzitutto, assicurati che le tue statistiche siano aggiornate. Questa dovrebbe davvero essere la prima cosa da fare quando una query sembra agire in modo strano o casuale. Il tuo DBA dovrebbe farlo, ma le cose accadono. Il solito modo per garantire statistiche aggiornate è di reindicizzare le tabelle, che non è necessariamente una cosa leggera da fare, ma ci sono anche opzioni per aggiornare le statistiche.

La seconda cosa a cui pensare è aggiungere indici lungo le linee dei suggerimenti di Remus. Con un indice migliore / diverso, un valore rispetto a un altro potrebbe essere più stabile e non variare in modo così sfrenato.

Se ciò non aiuta, la terza cosa da provare è forzare un nuovo piano ogni volta che si esegue l'istruzione, utilizzando la parola chiave RECOMPILE:

seleziona * da [tabella grande] dove serial_number = [un certo numero] ordina per test_date desc OPTION (RECOMPILE)

C'è un articolo che descrive una situazione simile qui . Francamente, avevo già visto RECOMPILE applicato alle stored procedure prima, ma sembra funzionare con istruzioni SELECT "regolari". Kimberly Tripp non mi ha mai guidato nel modo sbagliato.

Potresti anche esaminare la funzione chiamata " guide di piano ", ma è più complessa e potrebbe essere eccessiva.


Per coprire alcune di queste preoccupazioni: 1. Le statistiche sono state aggiornate, sono in fase di aggiornamento. 2. Abbiamo provato a indicizzare in diversi modi (coprendo indici, ecc.) Ma il problema sembra essere più legato order byall'uso specifico di un indice datetime. 3. Ho appena provato la tua idea con l'opzione RECOMPILE, non è riuscita, il che mi ha sorpreso un po ', speravo che funzionasse, anche se non so se sia una soluzione per la produzione.
DBtheDBA,
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.