Ausführen der Abfrage von hier aus , um die Deadlock-Ereignisse aus der erweiterten Standardereignissitzung zu entfernen
SELECT CAST (
REPLACE (
REPLACE (
XEventData.XEvent.value ('(data/value)[1]', 'varchar(max)'),
'<victim-list>', '<deadlock><victim-list>'),
'<process-list>', '</victim-list><process-list>')
AS XML) AS DeadlockGraph
FROM (SELECT CAST (target_data AS XML) AS TargetData
FROM sys.dm_xe_session_targets st
JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address
WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report';
Die Ausführung auf meinem Computer dauert ungefähr 20 Minuten. Die gemeldeten Statistiken sind
Table 'Worktable'. Scan count 0, logical reads 68121, physical reads 0, read-ahead reads 0,
lob logical reads 25674576, lob physical reads 0, lob read-ahead reads 4332386.
SQL Server Execution Times:
CPU time = 1241269 ms, elapsed time = 1244082 ms.
Wenn ich die WHERE
Klausel entferne , wird sie in weniger als einer Sekunde abgeschlossen und liefert 3.782 Zeilen.
Ebenso, wenn ich OPTION (MAXDOP 1)
zu der ursprünglichen Abfrage hinzufüge , die die Dinge auch mit den Statistiken beschleunigt, die jetzt massiv weniger Lob-Lesevorgänge anzeigen.
Table 'Worktable'. Scan count 0, logical reads 15, physical reads 0, read-ahead reads 0,
lob logical reads 6767, lob physical reads 0, lob read-ahead reads 6076.
SQL Server Execution Times:
CPU time = 639 ms, elapsed time = 693 ms.
Also meine Frage ist
Kann jemand erklären, was los ist? Warum ist der ursprüngliche Plan so katastrophal und gibt es eine verlässliche Möglichkeit, das Problem zu umgehen?
Zusatz:
Ich habe auch festgestellt, dass das Ändern der Abfrage INNER HASH JOIN
die Dinge in gewissem Maße verbessert (aber es dauert immer noch> 3 Minuten), da die DMV-Ergebnisse so klein sind, dass ich bezweifle, dass der Join-Typ selbst dafür verantwortlich ist und davon ausgeht, dass sich etwas anderes geändert hat. Statistiken dafür
Table 'Worktable'. Scan count 0, logical reads 30294, physical reads 0, read-ahead reads 0,
lob logical reads 10741863, lob physical reads 0, lob read-ahead reads 4361042.
SQL Server Execution Times:
CPU time = 200914 ms, elapsed time = 203614 ms.
Nach dem Auffüllen des erweiterten Ereignisringpuffers ( DATALENGTH
der XML
4.880.045 Byte enthielt und 1.448 Ereignisse enthielt) und dem Testen einer gekürzten Version der ursprünglichen Abfrage mit und ohne MAXDOP
Hinweis.
SELECT COUNT(*)
FROM (SELECT CAST (target_data AS XML) AS TargetData
FROM sys.dm_xe_session_targets st
JOIN sys.dm_xe_sessions s
ON s.address = st.event_session_address
WHERE [name] = 'system_health') AS Data
CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report'
SELECT*
FROM sys.dm_db_task_space_usage
WHERE session_id = @@SPID
Ergab die folgenden Ergebnisse
+-------------------------------------+------+----------+
| | Fast | Slow |
+-------------------------------------+------+----------+
| internal_objects_alloc_page_count | 616 | 1761272 |
| internal_objects_dealloc_page_count | 616 | 1761272 |
| elapsed time (ms) | 428 | 398481 |
| lob logical reads | 8390 | 12784196 |
+-------------------------------------+------+----------+
Es gibt einen deutlichen Unterschied in der Zuweisung von Tempdb zu der schnelleren, die anzeigt, dass 616
Seiten zugewiesen und freigegeben wurden. Dies ist die gleiche Anzahl von Seiten, die verwendet werden, wenn das XML auch in eine Variable eingefügt wird.
Für den langsamen Plan liegen diese Seitenzuordnungszahlen in Millionenhöhe. Die Abfrage dm_db_task_space_usage
während der Ausführung der Abfrage zeigt, dass ständig tempdb
Seiten zugewiesen und freigegeben werden, wobei zwischen 1.800 und 3.000 Seiten gleichzeitig zugewiesen werden.
quelle
WHERE
Klausel in den XQuery-Ausdruck verschieben. die Logik muss nicht entfernt werden , damit es schnell gehen:TargetData.nodes ('RingBufferTarget[1]/event[@name = "xml_deadlock_report"]')
. Trotzdem kenne ich XML-Interna nicht gut genug, um die von Ihnen gestellte Frage zu beantworten.Antworten:
Der Grund für den Leistungsunterschied liegt darin, wie skalare Ausdrücke in der Ausführungsengine behandelt werden. In diesem Fall ist der Ausdruck des Interesses:
Dieser Ausdruck Etikett wird definiert durch einen Compute Scalar - Operator (Knoten 11 in dem seriellen Plan, den Knoten 13 in dem parallelen Plan). Compute Scalar-Operatoren unterscheiden sich von anderen Operatoren (ab SQL Server 2005) darin, dass die von ihnen definierten Ausdrücke nicht unbedingt an der Position ausgewertet werden, an der sie im sichtbaren Ausführungsplan erscheinen. Die Auswertung kann verschoben werden, bis das Ergebnis der Berechnung von einem späteren Bediener benötigt wird.
In der vorliegenden Abfrage ist die
target_data
Zeichenfolge normalerweise groß, wodurch die Konvertierung von Zeichenfolge zuXML
teuer wird. In langsamen Plänen wird die zuXML
konvertierende Zeichenfolge jedes Mal ausgeführt, wenn ein späterer Operator, der das Ergebnis von benötigt, erneutExpr1000
gebunden wird.Das erneute Binden erfolgt an der Innenseite eines Joins mit verschachtelten Schleifen, wenn sich ein korrelierter Parameter (äußere Referenz) ändert.
Expr1000
ist eine äußere Referenz für die meisten der in diesem Ausführungsplan verbundenen verschachtelten Schleifen. Der Ausdruck wird von mehreren XML-Readern, sowohl von Stream-Aggregaten als auch von einem Startfilter, mehrfach referenziert. Abhängig von der Größe vonXML
kann die Anzahl der Konvertierungen der ZeichenfolgeXML
leicht in den Millionenbereich fallen.Die folgenden Aufruflisten zeigen Beispiele für die
target_data
Konvertierung der Zeichenfolge inXML
(ConvertStringToXMLForES
- wobei ES der Ausdrucksservice ist ):Startfilter
XML Reader (TVF Stream intern)
Aggregat streamen
Das Konvertieren der Zeichenfolge bei
XML
jeder erneuten Bindung eines dieser Operatoren erklärt den Leistungsunterschied, der bei den Plänen für verschachtelte Schleifen beobachtet wird. Dies ist unabhängig davon, ob Parallelität verwendet wird oder nicht. Es kommt einfach so vor, dass der Optimierer einen Hash-Join auswählt, wenn derMAXDOP 1
Hinweis angegeben wird. WennMAXDOP 1, LOOP JOIN
angegeben, ist die Leistung genauso schlecht wie beim parallelen Standardplan (bei dem der Optimierer verschachtelte Schleifen auswählt).Wie viel Leistung mit einem Hash-Join zunimmt, hängt davon ab, ob
Expr1000
auf der Build- oder der Testseite des Operators angezeigt wird. Die folgende Abfrage sucht den Ausdruck auf der Testseite:Ich habe die schriftliche Reihenfolge der Joins gegenüber der in der Frage gezeigten Version umgekehrt, da Join-Hinweise (
INNER HASH JOIN
oben) auch die Reihenfolge für die gesamte Abfrage erzwingen, so als obFORCE ORDER
sie angegeben worden wären . Die Umkehrung ist notwendig, um sicherzustellenExpr1000
, dass auf der Sondenseite erscheint. Der interessante Teil des Ausführungsplans ist:Mit dem auf der Testseite definierten Ausdruck wird der Wert zwischengespeichert:
Die Auswertung von
Expr1000
wird immer noch zurückgestellt, bis der erste Operator den Wert benötigt (der Startfilter im Stack-Trace oben), der berechnete Wert jedoch zwischengespeichert (CValHashCachedSwitch
) und für spätere Aufrufe durch die XML-Reader und Stream-Aggregate wiederverwendet wird. Die folgende Stapelverfolgung zeigt ein Beispiel für den zwischengespeicherten Wert, der von einem XML-Reader wiederverwendet wird.Wenn die Join-Reihenfolge so erzwungen wird, dass die Definition von
Expr1000
auf der Build-Seite des Hash-Joins erfolgt, ist die Situation anders:Ein Hash-Join liest seine Build-Eingabe vollständig, um eine Hash-Tabelle zu erstellen, bevor er nach Übereinstimmungen sucht. Infolgedessen müssen wir alle Werte speichern , nicht nur den pro Thread, an dem auf der Testseite des Plans gearbeitet wird. Die Hashverknüpfung verwendet daher eine
tempdb
Arbeitstabelle zum Speichern derXML
Daten, und jeder Zugriff auf das ErgebnisExpr1000
durch spätere Operatoren erfordert eine kostspielige Reise nachtempdb
:Im Folgenden finden Sie weitere Details zum langsamen Zugriffspfad:
Wenn ein Merge-Join erzwungen wird, werden die Eingabezeilen sortiert (eine Blockierungsoperation, genau wie die Build-Eingabe für einen Hash-Join). Dies führt zu einer ähnlichen Anordnung, bei der
tempdb
aufgrund der Datengröße ein langsamer Zugriff über eine sortierungsoptimierte Arbeitstabelle erforderlich ist.Pläne, die große Datenmengen bearbeiten, können aus allen möglichen Gründen problematisch sein, die aus dem Ausführungsplan nicht hervorgehen. Die Verwendung eines Hash-Joins (mit dem Ausdruck für die richtige Eingabe) ist keine gute Lösung. Es basiert auf undokumentiertem internen Verhalten, ohne Garantie, dass es nächste Woche auf die gleiche Weise funktioniert, oder auf einer geringfügig anderen Abfrage.
Die Botschaft ist, dass
XML
Manipulationen heute schwierig zu optimieren sein können. Das SchreibenXML
in eine variable oder temporäre Tabelle vor dem Zerkleinern ist eine viel zuverlässigere Lösung als alles, was oben gezeigt wurde. Ein Weg dies zu tun ist:Zum Schluss möchte ich noch Martins sehr schöne Grafik aus den Kommentaren unten hinzufügen:
quelle
@@IEAAXPEA_K
angezeigt werden.Das ist der Code aus meinem Artikel, der ursprünglich hier gepostet wurde:
http://www.sqlservercentral.com/articles/deadlock/65658/
Wenn Sie die Kommentare lesen, finden Sie eine Reihe von Alternativen, bei denen die auftretenden Leistungsprobleme nicht auftreten. Eine verwendet eine Modifikation dieser ursprünglichen Abfrage und die andere eine Variable, um das XML zu speichern, bevor es verarbeitet wird besser. (siehe meine Kommentare auf Seite 2) XML von den DMVs kann langsam verarbeitet werden, ebenso wie das Parsen von XML von der DMF für das Dateiziel, was häufig besser erreicht wird, indem die Daten zuerst in eine temporäre Tabelle eingelesen und dann verarbeitet werden. XML in SQL ist im Vergleich zur Verwendung von .NET oder SQLCLR langsam.
quelle
303 ms
und3249 lob reads
. 2012 musste ich auchand target_name='ring_buffer'
diese Version ergänzen , da es jetzt zwei Ziele zu geben scheint. Ich versuche immer noch, mir ein Bild davon zu machen, was genau es in der 20-minütigen Version macht.