In meinem vorherigen Beitrag habe ich verschiedene Methoden untersucht, um automatische Aktualisierungen von Statistiken zu verfolgen, um festzustellen, ob sie die Abfrageleistung beeinträchtigen. In der zweiten Hälfte des Beitrags habe ich Optionen eingefügt, von denen eine darin bestand, die Datenbankeinstellung „Statistiken automatisch aktualisieren asynchron“ zu aktivieren. In diesem Beitrag möchte ich untersuchen, wie sich die Abfrageleistung ändert, wenn die automatische Aktualisierung vor der Abfrageausführung erfolgt, und was mit der Leistung passiert, wenn die Aktualisierung asynchron ist.
Die Einrichtung
Ich habe mit einer Kopie der AdventureWorks2012-Datenbank begonnen und dann mithilfe dieses Skripts eine Kopie der SalesOrderHeader-Tabelle mit über 200 Millionen Zeilen erstellt. Die Tabelle hat einen Clustered-Index für SalesOrderID und einen Nonclustered-Index für CustomerID, OrderDate, SubTotal. [Hinweis:Wenn Sie wiederholte Tests durchführen, erstellen Sie an dieser Stelle eine Sicherungskopie dieser Datenbank, um Zeit zu sparen]. Nachdem ich die Daten geladen und den Nonclustered-Index erstellt hatte, überprüfte ich die Zeilenanzahl und berechnete, wie viele Zeilen (ungefähr) geändert werden müssten, um eine automatische Aktualisierung aufzurufen.
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');
Big_SalesOrderHeader CIX- und NCI-Informationen
Ich habe auch den aktuellen Statistik-Header für den Index überprüft:
DBCC SHOW_STATISTICS ('Sales.Big_SalesOrderHeader',[IX_Big_SalesOrderHeader_CustomerID_OrderDate_SubTotal]);
NCI-Statistiken:Zu Beginn
Ich habe dann die gespeicherte Prozedur erstellt, die ich zum Testen verwenden würde. Es ist ein einfaches Verfahren, das Sales.Big_SalesOrderHeader abfragt und Verkaufsdaten nach CustomerID und OrderDate zur Analyse zusammenfasst:
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
Schließlich habe ich vor dem Ausführen der gespeicherten Prozedur eine Sitzung mit erweiterten Ereignissen erstellt, damit ich die Abfragedauer mit sp_statement_starting und sp_statement_completed verfolgen konnte. Ich habe auch das Ereignis auto_stats hinzugefügt, da ich, obwohl ich nicht mit einer Aktualisierung gerechnet hatte, später dieselbe Sitzungsdefinition verwenden wollte.
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
Der Test
Ich habe die Sitzung für erweiterte Ereignisse gestartet und dann die gespeicherte Prozedur mehrmals ausgeführt, wobei ich verschiedene Kunden-IDs verwendet habe:
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
Ich habe die Ausführungsanzahl und den Plan überprüft, indem ich den Prozedurcache abgefragt habe:
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;
Plan-Cache:Beim Start
Abfrageplan für gespeicherte Prozeduren mit SQL Sentry Plan Explorer
Ich konnte sehen, dass der Plan am 08.04.2014 18:59:39.850 erstellt wurde. Mit dem Plan im Cache habe ich die Sitzung für erweiterte Ereignisse angehalten:
ALTER EVENT SESSION [StatsUpdate_QueryPerf] ON SERVER STATE = STOP;
Als nächstes fügte ich der Tabelle mit diesem Skript etwa 47 Millionen Datenzeilen hinzu, weit über dem Schwellenwert, der erforderlich ist, um die aktuellen Statistiken ungültig zu machen. Nach dem Hinzufügen der Daten habe ich die Anzahl der Zeilen in der Tabelle überprüft:
Big_SalesOrderHeader-CI:Nach dem Laden der Daten
Bevor ich meine gespeicherte Prozedur erneut ausführte, überprüfte ich den Plan-Cache, um sicherzustellen, dass sich nichts geändert hatte, und vergewisserte mich, dass die Statistiken noch nicht aktualisiert wurden. Denken Sie daran, dass die Statistiken, obwohl sie zu diesem Zeitpunkt ungültig gemacht wurden, erst aktualisiert werden, wenn eine Abfrage ausgeführt wird, die die Statistik verwendet (als Referenz:Verstehen, wann Statistiken automatisch aktualisiert werden). Für den letzten Schritt habe ich die Sitzung mit erweiterten Ereignissen erneut gestartet und dann die gespeicherte Prozedur mehrmals ausgeführt. Nach diesen Ausführungen habe ich den Plan-Cache erneut überprüft:
Plan-Cache:Nach dem Laden der Daten
Der execute_count ist wieder 8, und wenn wir uns die create_time des Plans ansehen, können wir sehen, dass er in 2014-04-08 19:32:52.913 geändert wurde. Wenn wir den Plan überprüfen, können wir sehen, dass er derselbe ist, obwohl der Plan neu kompiliert wurde:
Abfrageplan für gespeicherte Prozeduren mit SQL Sentry Plan Explorer
Analyse der Ausgabe von erweiterten Ereignissen
Ich nahm die erste Extended Events-Datei – bevor die Daten geladen wurden – und öffnete sie in SSMS und wendete dann einen Filter an, sodass nur Anweisungen aus der gespeicherten Prozedur aufgelistet wurden:
Extended Events Output:After Initial SP Execution
Sie können sehen, dass die gespeicherte Prozedur acht (8) ausgeführt wird, wobei die Abfragedauer leicht variiert.
Ich nahm die zweite Extended Events-Datei – nachdem die Daten geladen wurden – öffnete sie SSMS und filterte erneut, sodass nur Anweisungen aus der gespeicherten Prozedur sowie auto_stats-Ereignisse aufgelistet wurden:
Extended Events Output:SP Execution After Data Load
Die Ausgabe wird abgeschnitten, da nicht alles benötigt wird, um das Hauptergebnis anzuzeigen. Die blau hervorgehobenen Einträge stellen die erste Ausführung der gespeicherten Prozedur dar, und beachten Sie, dass es mehrere Schritte gibt – die Aktualisierung der Statistiken ist Teil der Ausführung. Die SELECT-Anweisung wird gestartet (attach_activity_id.seq =3), und die Aktualisierungen der Statistiken werden dann ausgeführt. In unserem Beispiel haben wir tatsächlich Aktualisierungen für drei Statistiken. Sobald die letzte Aktualisierung abgeschlossen ist (attach_activity_id.seq =11), wird die gespeicherte Prozedur gestartet und abgeschlossen (attach_activity_id.seq =13 und Attach_activity_id.seq =14). Interessanterweise gibt es ein zweites sp_statement_starting-Ereignis für die gespeicherte Prozedur (vermutlich wird das erste ignoriert), sodass die Gesamtdauer für die gespeicherte Prozedur ohne berechnet wird die Aktualisierung der Statistik.
In diesem Szenario führt die sofortige automatische Aktualisierung der Statistiken – d. h. wenn eine Abfrage ausgeführt wird, die ungültige Statistiken verwendet – dazu, dass die Abfrage länger ausgeführt wird, obwohl die Abfragedauer basierend auf dem sp_statement_completed-Ereignis immer noch weniger als 14000 beträgt. Das Endergebnis ist, dass es da ist ist kein Vorteil für die Abfrageleistung, da der Plan vor und nach der Statistikaktualisierung genau gleich ist. In diesem Szenario ändern sich der Abfrageplan und die Ausführungsdauer nicht, nachdem weitere Daten zur Tabelle hinzugefügt wurden, sodass die Aktualisierung der Statistiken nur die Leistung beeinträchtigt. Sehen wir uns nun an, was passiert, wenn wir die Option Statistik automatisch asynchron aktualisieren aktivieren.
Der Test, Version 2
Wir beginnen mit der Wiederherstellung des Backups, das ich erstellt habe, bevor wir mit dem ersten Test begonnen haben. Ich habe die gespeicherte Prozedur neu erstellt und dann die Datenbankoption geändert, um Statistiken asynchron zu aktualisieren:
USE [master]; GO ALTER DATABASE [AdventureWorks2012_Big] SET AUTO_UPDATE_STATISTICS_ASYNC ON WITH NO_WAIT GO
Ich habe die Sitzung für erweiterte Ereignisse gestartet und die gespeicherte Prozedur erneut mehrmals ausgeführt, wobei ich verschiedene Kunden-IDs verwendet habe:
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
Ich habe die Ausführungsanzahl und den Plan überprüft, indem ich den Prozedurcache abgefragt habe:
Plan-Cache:Zu Beginn, Test 2
Abfrageplan für gespeicherte Prozeduren mit SQL Sentry Plan Explorer
Für diesen Test wurde der Plan am 08.04.2014 21:15:55.490 erstellt. Ich habe die Sitzung mit erweiterten Ereignissen angehalten und erneut etwa 47 Millionen Datenzeilen zur Tabelle hinzugefügt, wobei ich dieselbe Abfrage wie zuvor verwendet habe.
Nachdem die Daten hinzugefügt worden waren, überprüfte ich den Plan-Cache, um sicherzustellen, dass sich nichts geändert hatte, und vergewisserte mich, dass die Statistiken noch nicht aktualisiert wurden. Schließlich habe ich die Sitzung für erweiterte Ereignisse erneut gestartet und die gespeicherte Prozedur dann weitere acht Mal ausgeführt. Ein letzter Blick in den Plan-Cache zeigte execute_count bei 16 und eine create_time von 2014-04-08 21:15:55.490. Die Ausführungszähler und die Erstellungszeit zeigen, dass die Statistiken nicht aktualisiert wurden, da der Plan noch nicht aus dem Cache geleert wurde (wenn dies der Fall wäre, hätten wir eine spätere Erstellungszeit und einen Ausführungszähler von 8).
Cache planen:Nach dem Laden der Daten, Test 2
Wenn wir die Ausgabe der erweiterten Ereignisse nach dem Laden der Daten in SSMS öffnen und erneut filtern, sodass wir nur Anweisungen aus der gespeicherten Prozedur sowie auto_stats-Ereignisse sehen, finden wir Folgendes (beachten Sie, dass die Ausgabe in zwei Screenshots unterteilt ist):
Extended Events Output:Test 2, SP Execution After Data Load, Teil I
Extended Events Output:Test 2, SP Execution After Data Load, Teil II
Die Ereignisse für die Ausführung des ersten Aufrufs der gespeicherten Prozedur sind blau hervorgehoben – sie beginnen am 08.04.2014 21:54:14.9480607 und es gibt sieben (7) Ereignisse. Beachten Sie, dass es drei (3) auto_stats-Ereignisse gibt, von denen aber keines wirklich abgeschlossen ist, wie wir gesehen haben, als die Option „Statistiken automatisch asynchron aktualisieren“ deaktiviert war. Sie werden feststellen, dass die automatische Aktualisierung für eine der Statistiken fast sofort beginnt (2014-04-08 21:54:14.9481288), und neben den drei Ereignissen steht der rote Text „Statistik-Update Nr. 1“. Diese Statistikaktualisierung endet am 08.04.2014 21:54:16.5392219, knapp zwei Sekunden nach dem Start, aber nachdem alle anderen Ausführungen der Prozedur abgeschlossen sind. Aus diesem Grund zeigt der execute_count von sys.dm_exec_query_stats 16. Aus der XE-Ausgabe können wir sehen, dass die anderen Statistikaktualisierungen dann abgeschlossen sind (Stat Update #2 und Stat Update #3). Alle Aktualisierungen erfolgen asynchron zur Ausführung der anfänglichen gespeicherten Prozedur.
Zusammenfassung
Wie Sie sehen können, können automatische Aktualisierungen von Statistiken die Abfrageleistung negativ beeinflussen. Das Ausmaß der Auswirkungen hängt von der Datenmenge ab, die gelesen werden muss, um die Statistik zu aktualisieren, sowie von den Systemressourcen. In einigen Fällen erhöht sich die Abfrageleistung nur um Millisekunden und ist für Benutzer höchstwahrscheinlich nicht wahrnehmbar. In anderen Fällen kann die Dauer dramatisch ansteigen, was sich dann auf die Endbenutzererfahrung auswirkt. Falls sich der Abfrageplan nach einer Statistikaktualisierung nicht ändert, sollten Sie die Option „Statistiken automatisch asynchron aktualisieren“ aktivieren, um die Auswirkungen auf die Abfrageleistung abzumildern.