In meinem vorherigen Beitrag habe ich CXPACKET-Wartezeiten und Möglichkeiten zur Verhinderung oder Begrenzung von Parallelität besprochen. Ich habe auch erklärt, wie der Steuer-Thread in einem parallelen Betrieb immer einen CXPACKET-Wartevorgang registriert und dass manchmal Nicht-Steuer-Threads auch CXPACKET-Wartevorgänge registrieren können. Dies kann passieren, wenn einer der Threads beim Warten auf eine Ressource blockiert ist (also alle anderen Threads vor ihm beendet werden und CXPACKET-Wartevorgänge ebenfalls registrieren) oder wenn Kardinalitätsschätzungen falsch sind. In diesem Beitrag möchte ich letzteres untersuchen.
Wenn Kardinalitätsschätzungen falsch sind, erhalten die parallelen Threads, die die Abfragearbeit ausführen, ungleichmäßige Arbeitsmengen. Der typische Fall ist, dass einem Thread die ganze Arbeit gegeben wird, oder viel mehr Arbeit als die anderen Threads. Dies bedeutet, dass die Threads, die die Verarbeitung ihrer Zeilen beenden (falls ihnen überhaupt welche gegeben wurden), bevor der langsamste Thread ein CXPACKET von dem Moment an registriert, an dem sie fertig sind, bis der langsamste Thread beendet ist. Dieses Problem kann zu einer scheinbaren Explosion von CXPACKET-Wartezeiten führen und wird allgemein als verzerrte Parallelität bezeichnet , weil die Verteilung der Arbeit zwischen den parallelen Threads verzerrt ist, nicht einmal.
Beachten Sie, dass Consumer-Threads in SQL Server 2016 SP2 und SQL Server 2017 RTM CU3 keine CXPACKET-Wartevorgänge mehr registrieren. Sie registrieren CXCONSUMER Waits, die harmlos sind und ignoriert werden können. Dadurch soll die Anzahl der generierten CXPACKET-Wartevorgänge reduziert werden, und die verbleibenden sind mit größerer Wahrscheinlichkeit umsetzbar.
Beispiel für schiefe Parallelität
Ich werde durch ein erfundenes Beispiel gehen, um zu zeigen, wie man solche Fälle identifiziert.
Zunächst erstelle ich ein Szenario, in dem eine Tabelle völlig ungenaue Statistiken enthält, indem ich die Anzahl der Zeilen und Seiten in einer STATISTIK AKTUALISIEREN manuell festlege Anweisung (tun Sie dies nicht in der Produktion!):
USE [master]; GO IF DB_ID (N'ExecutionMemory') IS NOT NULL BEGIN ALTER DATABASE [ExecutionMemory] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [ExecutionMemory]; END GO CREATE DATABASE [ExecutionMemory]; GO USE [ExecutionMemory]; GO CREATE TABLE dbo.[Test] ( [RowID] INT IDENTITY, [ParentID] INT, [CurrentValue] NVARCHAR (100), CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED ([RowID])); GO INSERT INTO dbo.[Test] ([ParentID], [CurrentValue]) SELECT CASE WHEN ([t1].[number] % 3 = 0) THEN [t1].[number] – [t1].[number] % 6 ELSE [t1].[number] END, 'Test' + CAST ([t1].[number] % 2 AS VARCHAR(11)) FROM [master].[dbo].[spt_values] AS [t1] WHERE [t1].[type] = 'P'; GO UPDATE STATISTICS dbo.[Test] ([PK_Test]) WITH ROWCOUNT = 10000000, PAGECOUNT = 1000000; GO
Meine Tabelle hat also nur ein paar tausend Zeilen, aber ich habe sie mit 10 Millionen Zeilen vorgetäuscht.
Jetzt erstelle ich eine erfundene Abfrage, um die obersten 500 Zeilen auszuwählen, die parallel ausgeführt werden, da sie davon ausgeht, dass Millionen von Zeilen gescannt werden müssen.
USE [ExecutionMemory]; GO SET NOCOUNT ON; GO DECLARE @CurrentValue NVARCHAR (100); WHILE (1=1) SELECT TOP (500) @CurrentValue = [CurrentValue] FROM dbo.[Test] ORDER BY NEWID() DESC; GO
Und das zum Laufen bringen.
Anzeigen der CXPACKET-Wartezeiten
Jetzt kann ich mir die CXPACKET-Wartevorgänge ansehen, die auftreten, indem ich ein einfaches Skript verwende, um mir die sys.dm_os_waiting_tasks anzusehen DMV:
SELECT [owt].[session_id], [owt].[exec_context_id], [owt].[wait_duration_ms], [owt].[wait_type], [owt].[blocking_session_id], [owt].[resource_description], [er].[database_id], [eqp].[query_plan] FROM sys.dm_os_waiting_tasks [owt] INNER JOIN sys.dm_exec_sessions [es] ON [owt].[session_id] = [es].[session_id] INNER JOIN sys.dm_exec_requests [er] ON [es].[session_id] = [er].[session_id] OUTER APPLY sys.dm_exec_sql_text ([er].[sql_handle]) [est] OUTER APPLY sys.dm_exec_query_plan ([er].[plan_handle]) [eqp] WHERE [es].[is_user_process] = 1 ORDER BY [owt].[session_id], [owt].[exec_context_id];
Wenn ich dies ein paar Mal ausführe, sehe ich schließlich einige Ergebnisse, die eine verzerrte Parallelität zeigen (ich habe den Handle-Link des Abfrageplans entfernt und die Ressourcenbeschreibung aus Gründen der Übersichtlichkeit gekürzt, und beachten Sie, dass ich den Code eingefügt habe, um den SQL-Text abzurufen, wenn Sie das möchten auch):
session_id | exec_context_id | wait_duration_ms | wait_type | blocking_session_id | resource_description | Datenbank-ID |
---|---|---|---|---|---|---|
56 | 0 | 1 | CXPACKET | NULL | exchangeEvent | 13 |
56 | 1 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 3 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 4 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 5 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 6 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
56 | 7 | 1 | CXPACKET | 56 | exchangeEvent | 13 |
Ergebnisse zeigen verzerrte Parallelität in Aktion
Der Kontroll-Thread ist derjenige mit exec_context_id auf 0 gesetzt. Die anderen parallelen Threads sind diejenigen mit exec_context_id höher als 0, und sie zeigen alle CXPACKET-Wartezeiten außer einer (beachten Sie, dass exec_context_id = 2
fehlt in der Liste). Sie werden feststellen, dass sie alle ihre eigene session_id auflisten als derjenige, der sie blockiert, und das ist richtig, weil alle Threads auf einen anderen Thread von ihrer eigenen session_id warten fertigstellen. Die database_id ist die Datenbank, in deren Kontext die Abfrage ausgeführt wird, nicht unbedingt die Datenbank, in der das Problem liegt, aber normalerweise, es sei denn, die Abfrage verwendet eine dreiteilige Benennung, um in einer anderen Datenbank ausgeführt zu werden.
Anzeigen des Kardinalitätsschätzungsproblems
Mit dem query_plan Spalte in der Abfrageausgabe (die ich aus Gründen der Übersichtlichkeit entfernt habe), können Sie darauf klicken, um den grafischen Plan aufzurufen, und dann mit der rechten Maustaste klicken und View with SQL Sentry Plan Explorer auswählen. Dies zeigt sich wie folgt:
Ich sehe sofort, dass es ein Problem mit der Kardinalitätsschätzung gibt, da die tatsächlichen Zeilen für den Clustered Index Scan nur 2.048 betragen, verglichen mit 10.000.000 geschätzten (geschätzten) Zeilen.
Wenn ich darüber scrolle, kann ich die Verteilung der Zeilen über die verwendeten parallelen Threads sehen:
Und siehe da, nur ein einziger Thread arbeitete während des parallelen Teils des Plans – derjenige, der nicht in den sys.dm_os_waiting_tasks auftauchte Ausgabe oben.
In diesem Fall besteht die Lösung darin, die Statistiken für die Tabelle zu aktualisieren.
In meinem erfundenen Beispiel funktioniert das nicht, da keine Änderungen an der Tabelle vorgenommen wurden, also führe ich das Setup-Skript erneut aus und lasse STATISTIK AKTUALISIEREN weg Erklärung.
Der Abfrageplan wird dann zu:
Wo es kein Kardinalitätsproblem und auch keine Parallelität gibt – Problem gelöst!
Zusammenfassung
Wenn Sie sehen, dass CXPACKET-Wartezeiten auftreten, können Sie mit der oben beschriebenen Methode leicht auf verzerrte Parallelität prüfen. Alle Fälle, die ich gesehen habe, waren auf die eine oder andere Art von Kardinalitätsschätzungsproblemen zurückzuführen, und oft geht es einfach darum, Statistiken zu aktualisieren.
In Bezug auf allgemeine Wartestatistiken finden Sie weitere Informationen zu deren Verwendung für die Fehlerbehebung in:
- Meine SQLskills-Blogbeitragsserie, beginnend mit Wait-Statistiken, oder sagen Sie mir bitte, wo es wehtut
- Meine Wait Types und Latch Classes Bibliothek hier
- Mein Pluralsight-Online-Schulungskurs SQL Server:Fehlerbehebung bei der Leistung mithilfe von Wartestatistiken
- SQL-Sentry
Bis zum nächsten Mal, viel Spaß bei der Fehlersuche!