Nel mio post precedente, ho esplorato diversi metodi per tenere traccia degli aggiornamenti automatici alle statistiche per determinare se stavano influenzando le prestazioni delle query. Nella seconda metà del post ho incluso delle opzioni, una delle quali era quella di abilitare l'impostazione del database di aggiornamento automatico delle statistiche in modo asincrono. In questo post, voglio esaminare come cambiano le prestazioni della query quando l'aggiornamento automatico si verifica prima dell'esecuzione della query e cosa succede alle prestazioni se l'aggiornamento è asincrono.
L'allestimento
Ho iniziato con una copia del database AdventureWorks2012, quindi ho creato una copia della tabella SalesOrderHeader con oltre 200 milioni di righe utilizzando questo script. La tabella ha un indice cluster su SalesOrderID e un indice non cluster su CustomerID, OrderDate, SubTotal. [Nota:se hai intenzione di eseguire test ripetuti, fai un backup di questo database a questo punto per risparmiare tempo]. Dopo aver caricato i dati e creato l'indice non cluster, ho verificato il conteggio delle righe e calcolato quante righe (circa) avrebbero dovuto essere modificate per richiamare un aggiornamento automatico.
SELECT OBJECT_NAME([p].[object_id]) [TableName], [si].[name] [IndexName], [au].[type_desc] [Type], [p].[rows] [RowCount], ([p].[rows]*.20) + 500 [UpdateThreshold], [au].total_pages [PageCount], (([au].[total_pages]*8)/1024)/1024 [TotalGB] FROM [sys].[partitions] [p] JOIN [sys].[allocation_units] [au] ON [p].[partition_id] = [au].[container_id] JOIN [sys].[indexes] [si] on [p].[object_id] = [si].object_id and [p].[index_id] = [si].[index_id] WHERE [p].[object_id] = OBJECT_ID(N'Sales.Big_SalesOrderHeader');
Informazioni Big_SalesOrderHeader CIX e NCI
Ho anche verificato l'intestazione delle statistiche correnti per l'indice:
DBCC SHOW_STATISTICS ('Sales.Big_SalesOrderHeader',[IX_Big_SalesOrderHeader_CustomerID_OrderDate_SubTotal]);
Statistiche NCI:all'inizio
Ho quindi creato la procedura memorizzata che avrei utilizzato per il test. È una procedura semplice che interroga Sales.Big_SalesOrderHeader e aggrega i dati di vendita per CustomerID e OrderDate per l'analisi:
CREATE PROCEDURE Sales.usp_GetCustomerStats @CustomerID INT, @StartDate DATETIME, @EndDate DATETIME AS BEGIN SET NOCOUNT ON; SELECT CustomerID, DATEPART(YEAR, OrderDate), DATEPART(MONTH, OrderDate), COUNT([SalesOrderID]) as Computed FROM [Sales].[Big_SalesOrderHeader] WHERE CustomerID = @CustomerID AND OrderDate BETWEEN @StartDate and @EndDate GROUP BY CustomerID, DATEPART(YEAR, OrderDate), DATEPART(MONTH, OrderDate) ORDER BY DATEPART(YEAR, OrderDate), DATEPART(MONTH, OrderDate); END
Infine, prima di eseguire la stored procedure, ho creato una sessione di eventi estesi in modo da poter tenere traccia della durata della query utilizzando sp_statement_starting e sp_statement_completed. Ho anche aggiunto l'evento auto_stats, perché anche se non mi aspettavo che si verificasse un aggiornamento, volevo utilizzare la stessa definizione di sessione in un secondo momento.
CREATE EVENT SESSION [StatsUpdate_QueryPerf] ON SERVER ADD EVENT sqlserver.auto_stats, ADD EVENT sqlserver.sp_statement_completed( SET collect_statement=(1) ), ADD EVENT sqlserver.sp_statement_starting ADD TARGET package0.event_file( SET filename=N'C:\temp\StatsUpdate_QueryPerf.xel' ) WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS, MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF); GO
Il test
Ho avviato la sessione degli eventi estesi, quindi ho eseguito la stored procedure più volte, utilizzando CustomerID diversi:
ALTER EVENT SESSION [StatsUpdate_QueryPerf] ON SERVER STATE = START; GO EXEC Sales.usp_GetCustomerStats 11331, '2012-08-01 00:00:00.000', '2012-08-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats 11330, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats 11506, '2012-11-01 00:00:00.000', '2012-11-30 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats 17061, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats 11711, '2013-03-01 00:00:00.000', '2013-03-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats 15131, '2013-02-01 00:00:00.000', '2013-02-28 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats 29837, '2012-10-01 00:00:00.000', '2012-10-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats 15750, '2013-03-01 00:00:00.000', '2013-03-31 23:59:59.997' GO
Ho verificato il conteggio delle esecuzioni e il piano interrogando la cache delle procedure:
SELECT OBJECT_NAME([st].[objectid]), [st].[text], [qs].[execution_count], [qs].[creation_time], [qs].[last_execution_time], [qs].[min_worker_time], [qs].[max_worker_time], [qs].[min_logical_reads], [qs].[max_logical_reads], [qs].[min_elapsed_time], [qs].[max_elapsed_time], [qp].[query_plan] FROM [sys].[dm_exec_query_stats] [qs] CROSS APPLY [sys].[dm_exec_sql_text]([qs].plan_handle) [st] CROSS APPLY [sys].[dm_exec_query_plan]([qs].plan_handle) [qp] WHERE [st].[text] LIKE '%usp_GetCustomerStats%' AND OBJECT_NAME([st].[objectid]) IS NOT NULL;
Cache del piano:all'inizio
Piano di query per stored procedure, utilizzando SQL Sentry Plan Explorer
Ho potuto vedere che il piano è stato creato il 2014-04-08 18:59:39.850. Con il piano in cache, ho interrotto la sessione degli eventi estesi:
ALTER EVENT SESSION [StatsUpdate_QueryPerf] ON SERVER STATE = STOP;
Successivamente ho aggiunto circa 47 milioni di righe di dati alla tabella utilizzando questo script, ben oltre la soglia necessaria per invalidare le statistiche correnti. Dopo aver aggiunto i dati, ho verificato il numero di righe nella tabella:
CI Big_SalesOrderHeader:dopo il caricamento dei dati
Prima di eseguire nuovamente la procedura memorizzata, ho controllato la cache del piano per assicurarmi che nulla fosse cambiato e verificato che le statistiche non fossero state ancora aggiornate. Ricorda, anche se le statistiche sono state invalidate a questo punto, non si aggiorneranno fino a quando non verrà eseguita una query che utilizza la statistica (per riferimento:Capire quando le statistiche si aggiorneranno automaticamente). Per il passaggio finale, ho avviato nuovamente la sessione degli eventi estesi, quindi ho eseguito la stored procedure più volte. Dopo queste esecuzioni, ho controllato di nuovo la cache del piano:
Pianificare la cache:dopo il caricamento dei dati
Il conteggio_esecuzione è di nuovo 8, e se osserviamo il tempo di creazione del piano, possiamo vedere che è cambiato in 2014-04-08 19:32:52.913. Se controlliamo il piano, possiamo vedere che è lo stesso, anche se il piano è stato ricompilato:
Piano di query per stored procedure, utilizzando SQL Sentry Plan Explorer
Analisi dell'output di eventi estesi
Ho preso il primo file degli eventi estesi, prima del caricamento dei dati, e l'ho aperto in SSMS, quindi ho applicato un filtro in modo che fossero elencate solo le istruzioni della procedura memorizzata:
Output eventi estesi:dopo l'esecuzione SP iniziale
Puoi vedere che ci sono otto (8) esecuzioni della procedura memorizzata, con durate delle query che variano leggermente.
Ho preso il secondo file degli eventi estesi, dopo che i dati sono stati caricati, l'ho aperto SSMS e l'ho filtrato di nuovo in modo che fossero elencate solo le istruzioni della procedura memorizzata, oltre agli eventi auto_stats:
Output eventi estesi:esecuzione SP dopo il caricamento dei dati
L'output viene troncato, poiché non è tutto necessario per mostrare il risultato principale. Le voci evidenziate in blu rappresentano la prima esecuzione della stored procedure e si noti che sono presenti più passaggi:l'aggiornamento delle statistiche fa parte dell'esecuzione. Viene avviata l'istruzione SELECT (attach_activity_id.seq =3) e quindi vengono eseguiti gli aggiornamenti delle statistiche. Nel nostro esempio, abbiamo effettivamente aggiornamenti a tre statistiche. Una volta completato l'ultimo aggiornamento (attach_activity_id.seq =11), la procedura memorizzata viene avviata e completata (attach_activity_id.seq =13 e attach_activity_id.seq =14). È interessante notare che esiste un secondo evento sp_statement_starting per la stored procedure (presumibilmente il primo viene ignorato), quindi la durata totale della stored procedure viene calcolata senza l'aggiornamento delle statistiche.
In questo scenario, l'aggiornamento automatico immediato delle statistiche, ovvero quando viene eseguita una query che utilizza statistiche non valide, fa sì che la query venga eseguita più a lungo, anche se la durata della query basata sull'evento sp_statement_completed è ancora inferiore a 14000. Il risultato finale è che esiste non è un vantaggio per le prestazioni delle query, poiché il piano è esattamente lo stesso prima e dopo l'aggiornamento delle statistiche. In questo scenario, il piano della query e la durata dell'esecuzione non cambiano dopo l'aggiunta di più dati alla tabella, quindi l'aggiornamento delle statistiche ne ostacola solo le prestazioni. Ora vediamo cosa succede quando abilitiamo l'opzione Aggiornamento automatico statistiche in modo asincrono.
Il test, versione 2
Iniziamo ripristinando il backup che ho eseguito prima di iniziare il primo test. Ho ricreato la procedura memorizzata e quindi ho modificato l'opzione del database per aggiornare le statistiche in modo asincrono:
USE [master]; GO ALTER DATABASE [AdventureWorks2012_Big] SET AUTO_UPDATE_STATISTICS_ASYNC ON WITH NO_WAIT GO
Ho avviato la sessione degli eventi estesi e di nuovo eseguito la stored procedure più volte, utilizzando diversi CustomerID:
ALTER EVENT SESSION [StatsUpdate_QueryPerf] ON SERVER STATE = START; GO EXEC Sales.usp_GetCustomerStats11331, '2012-08-01 00:00:00.000', '2012-08-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats11330, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats11506, '2012-11-01 00:00:00.000', '2012-11-30 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats17061, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats11711, '2013-03-01 00:00:00.000', '2013-03-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats15131, '2013-02-01 00:00:00.000', '2013-02-28 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats29837, '2012-10-01 00:00:00.000', '2012-10-31 23:59:59.997' GO EXEC Sales.usp_GetCustomerStats15750, '2013-03-01 00:00:00.000', '2013-03-31 23:59:59.997' GO
Ho verificato il conteggio delle esecuzioni e il piano interrogando la cache delle procedure:
Cache del piano:all'inizio, test 2
Piano di query per stored procedure, utilizzando SQL Sentry Plan Explorer
Per questo test, il piano è stato creato il 2014-04-08 21:15:55.490. Ho interrotto la sessione degli eventi estesi e ho aggiunto di nuovo circa 47 milioni di righe di dati alla tabella, utilizzando la stessa query di prima.
Una volta aggiunti i dati, ho controllato la cache del piano per assicurarmi che nulla fosse cambiato e ho verificato che le statistiche non fossero ancora aggiornate. Infine, ho avviato nuovamente la sessione degli eventi estesi, quindi ho eseguito la procedura memorizzata altre otto volte. Un'ultima sbirciatina nella cache del piano ha mostrato un conteggio_esecuzione a 16 e un tempo di creazione di 2014-04-08 21:15:55.490. Il conteggio_esecuzione e il tempo di creazione dimostrano che le statistiche non sono state aggiornate, poiché il piano non è stato ancora cancellato dalla cache (se lo fosse, avremmo un tempo di creazione successivo e un conteggio_esecuzione di 8).
Pianificare la cache:dopo il caricamento dei dati, test 2
Se apriamo l'output degli eventi estesi da dopo il caricamento dei dati in SSMS e filtriamo nuovamente in modo da vedere solo le istruzioni della procedura memorizzata, oltre agli eventi auto_stats, troviamo questo (notare che l'output è suddiviso in due schermate):
Output eventi estesi:test 2, esecuzione SP dopo il caricamento dei dati, parte I
Output eventi estesi:test 2, esecuzione SP dopo il caricamento dei dati, parte II
Gli eventi per l'esecuzione della prima chiamata della stored procedure sono evidenziati in blu – iniziano alle 2014-04-08 21:54:14.9480607 e sono sette (7) eventi. Tieni presente che ci sono tre (3) eventi auto_stats, ma nessuno di essi viene effettivamente completato, come abbiamo visto quando l'opzione Aggiornamento automatico delle statistiche in modo asincrono è stata disabilitata. Noterai che l'aggiornamento automatico inizia quasi immediatamente per una delle statistiche (2014-04-08 21:54:14.9481288), e i tre eventi hanno il testo rosso "Aggiornamento statistiche n. 1" accanto a loro. L'aggiornamento delle statistiche termina alle 2014-04-08 21:54:16.5392219, poco meno di due secondi dopo l'inizio, ma dopo che tutte le altre esecuzioni della procedura sono state completate. Questo è il motivo per cui l'esecuzione_count da sys.dm_exec_query_stats mostra 16. Dall'output di XE, possiamo vedere che gli altri aggiornamenti delle statistiche vengono completati (Aggiornamento Stat n. 2 e Aggiornamento Stat n. 3). Tutti gli aggiornamenti sono asincroni rispetto all'esecuzione della stored procedure iniziale.
Riepilogo
Come puoi vedere, gli aggiornamenti automatici delle statistiche possono influire negativamente sulle prestazioni delle query. Il grado di impatto dipenderà dalla quantità di dati da leggere per aggiornare la statistica e dalle risorse di sistema. In alcuni casi, le prestazioni delle query aumentano solo di millisecondi ed è molto probabilmente impercettibile per gli utenti. Altre volte, la durata può aumentare notevolmente, il che influisce sull'esperienza dell'utente finale. Nel caso in cui il piano di query non cambi dopo un aggiornamento delle statistiche, vale la pena considerare di abilitare l'opzione di aggiornamento automatico delle statistiche in modo asincrono, per mitigare l'impatto sulle prestazioni della query.