Scarse prestazioni della tabella temporale su valori più vecchi


8

Sto riscontrando uno strano problema che si verifica quando si accede a record storici all'interno di una tabella temporale. Le query che accedono alle voci precedenti nella tabella temporale tramite la sotto-clausola AS OF richiedono più tempo delle query sulle voci storiche recenti.

La tabella storica è stata generata da SQL Server (include un indice cluster sulle colonne della data e utilizza la compressione della pagina), ho aggiunto 50 milioni di righe alla tabella storica e le mie query stavano recuperando circa 25.000 righe.

Ho provato a determinare la causa principale del problema ma non sono stato in grado di identificarlo. Finora ho testato:

  • Creazione di una tabella di test con 50 milioni di righe con un indice cluster per vedere se il rallentamento era semplicemente dovuto al volume. Sono stato in grado di recuperare 25K righe a tempo costante (~ 400ms).
  • Rimozione della compressione della pagina dalla tabella storica. Ciò non ha avuto alcun effetto sul tempo di recupero ma ha aumentato in modo significativo le dimensioni della tabella.
  • Ho provato ad accedere direttamente alle righe della tabella della cronologia utilizzando una colonna ID rispetto alle colonne della data. Questo è dove le cose erano un po 'più interessanti. Potrei accedere alle righe più vecchie nella tabella a ~ 400ms dove come con la sotto-clausola AS OF occorrerebbero ~ 1200ms. Ho provato a filtrare sulla mia tabella di test nella colonna della data e ho notato un rallentamento simile rispetto al filtro sulla colonna ID. Questo mi porta a credere che i confronti delle date siano alla base di alcuni rallentamenti.

Voglio approfondire questo aspetto, ma voglio anche assicurarmi di non abbaiare sull'albero sbagliato. Innanzitutto, qualcun altro ha riscontrato questo stesso comportamento durante l'accesso a dati storici meno recenti in una tabella temporale (abbiamo notato che i rallentamenti hanno superato solo 10 milioni di righe)? In secondo luogo, quali sono alcune strategie che posso utilizzare per isolare ulteriormente la causa principale del problema di prestazioni (ho appena iniziato a esaminare i piani di esecuzione ma per me è ancora un po 'enigmatico)?

Piani di esecuzione

Queste sono semplici query di recupero: la prima accede alle righe più vecchie, la seconda accede alle righe più recenti.

Righe precedenti ~ 1200 ms tempo di esecuzione

Righe recenti ~ tempo di esecuzione 350ms

Dettagli della tabella

Queste sono le colonne nella tabella temporale. La tabella cronologica ha le stesse colonne ma non ha una chiave primaria (secondo i requisiti della tabella cronologica): Colonna da tavolo temporale

Di seguito sono riportati gli indici nella tabella della cronologia: Indici nella tabella della cronologia

Risposte:


6

In un commento di Zane sulla tua domanda, ha dichiarato:

... Sembra che parte del tuo problema sia che stai leggendo 50 milioni di righe per restituire 20K nel piano.

Questo è, in effetti, il problema. Non è disponibile alcun indice per inviare alcuni o tutti i predicati al motore di archiviazione. Microsoft consiglia questa strategia di indicizzazione di base per le tabelle temporali nell'articolo Documenti Considerazioni e limitazioni sulla tabella temporale :

Una strategia di indicizzazione ottimale includerà un indice di archivio di colonne cluster e / o un indice di archivio righe B-tree nella tabella corrente e un indice di archivio di colonne cluster nella tabella cronologia per dimensioni e prestazioni di archiviazione ottimali. Se si crea / utilizza la propria tabella di cronologia, si consiglia vivamente di creare questo tipo di indice costituito da colonne di periodo che iniziano con la colonna di fine periodo per accelerare l'interrogazione temporale e accelerare le query che fanno parte della coerenza dei dati dai un'occhiata. La tabella della cronologia predefinita ha un indice del rowstore cluster creato per te in base alle colonne del periodo (fine, inizio). Come minimo, si consiglia un indice rowstore non cluster

Il fraseggio è un po 'confuso (per me, comunque). Ma da asporto è possibile creare questi indici per migliorare alcune prestazioni, se non addirittura parecchio:

Indice NC nella tabella corrente, che porta con SysEndTime:

CREATE NONCLUSTERED INDEX IX_SysEndTime_SysStartTime 
ON dbo.Benefits (SysEndTime, SysStartTime)
/*INCLUDE (ideally, include your other important fields here)*/;

Ciò ti consentirà di evitare di leggere alcune delle righe della tabella corrente cercando l'ora di fine appropriata.

CCI nella tabella cronologica

CREATE CLUSTERED COLUMNSTORE INDEX ix_BenefitsHistory
ON dbo.BenefitsHistory
WITH (DROP_EXISTING = ON);

Ciò ti consentirà di ottenere la modalità batch nella tabella della cronologia, il che dovrebbe rendere le scansioni molto più veloci.

Indice NC nella tabella corrente, che porta con SysStartTime:

Vedi la risposta di Paul alla domanda Il modo più efficiente per recuperare intervalli di date per maggiori dettagli sul perché l'indicizzazione per le query sull'intervallo di date è difficile. Sulla base della logica lì, ha senso aggiungere un altro indice NC nella tabella corrente che conduce con SysStartTime, in modo che l'ottimizzatore possa scegliere quale utilizzare in base alle statistiche e ai parametri specifici della query:

CREATE NONCLUSTERED INDEX IX_SysStartTime_SysEndTime
ON dbo.Benefits (SysStartTime, SysEndTime)
/*INCLUDE (ideally, include your other important fields here)*/;

La creazione dei 3 indici descritti sopra ha fatto una differenza significativa nell'uso delle risorse nei miei casi di test. Ho impostato un caso di test che esegue due query che restituiscono 1,5 milioni di righe totali. Sia la cronologia che le tabelle correnti hanno 50 milioni di righe).

Nota: per ridurre l'overhead di SSMS, ho eseguito il test con l'opzione "Elimina risultati dopo l'esecuzione" abilitata.

Piano di esecuzione: indici predefiniti

Letture logiche: 1.330.612
Tempo CPU: 00: 00: 14.718
Tempo trascorso: 00: 00: 06.198

Piano di esecuzione - Con gli indici sopra descritti

Letture logiche: 27.656 (8.111 righe store + 19.545 columnstore)
Tempo CPU: 00: 00: 01.828
Tempo trascorso: 00: 00: 01.150

Come puoi vedere, tutte e 3 le misure sono diminuite in modo significativo, incluso il tempo totale trascorso, da 6 secondi a 1 secondo.


L'altra opzione presentata dall'articolo di Documenti è quella di rinunciare ai due indici NC nella tabella corrente a favore di un indice columnstore cluster. Nel mio test, le prestazioni erano molto simili alla soluzione di indicizzazione sopra descritta.


2

La FOR SYSTEM TIME AS OFclausola tenta di restituire il set di dati esistente al momento indicato. Ciò significa che gli aggiornamenti devono essere ripristinati internamente, le eliminazioni devono essere "eliminate" e gli inserti devono essere ignorati, in base all'ora di sistema della richiesta.

Più in passato è il momento AS OF, più lavoro deve essere convalidato per garantire che la tabella temporale sia come esisteva all'ora di sistema specificata, e quindi più tempo richiederà la query.

Se la tabella dei dati è solo una tabella di registrazione e non vengono apportate modifiche ai dati, l'utilizzo della data registrata e un indice restituiranno i dati più rapidamente e in modo più coerente. Non è necessario utilizzare le funzioni temporali in questo caso. Tuttavia, se vengono apportate modifiche alle righe (diverse dagli inserimenti), l'utilizzo della funzionalità della tabella temporale è l'unico modo per restituire i dati esatti richiesti (lo stato della tabella esistente in quel momento specifico) e devo solo accettare l'overhead aggiuntivo delle query temporali.

Nota: i "rollback" non sono rollback effettivi. Le tabelle temporali utilizzano due tabelle: una tabella corrente e una tabella cronologia. Quando viene modificata una riga, una copia della versione precedente viene inserita nella tabella Cronologia con l'intervallo di tempo in cui la riga era valida. Se inserisci una riga al 20/10/2018 10: 20: 20.18, aggiorna un valore al 25/10/2018 10: 25: 20.18 e lo aggiorna nuovamente al 12/01/2018 12: 01: 20.18, hai l'ultima versione della riga nella tabella corrente con una data di inizio del 12/01/2018 12: 01: 20.18 e due righe nella tabella della cronologia con intervalli validi dal 20/10 al 25/10/2018 e 10 / 25 al 12/01/2018


Grazie per la risposta! Ciò ha sicuramente un senso intuitivo, ma non ho trovato alcuna menzione di quel tipo di comportamento nei documenti che ho letto (ho solo approfondito le basi della tabella temporale nei documenti di MS). Conosci qualche documentazione che descriva il comportamento in modo un po 'più dettagliato?
Ebrahim Behbahani,
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.