ここからのクエリ を実行して、デフォルトの拡張イベントセッションからデッドロックイベントを引き出します
_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';
_
私のマシンで完了するのに約20分かかります。報告される統計は
_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.
_
WHERE
句を削除すると、1秒未満で完了し、3,782行が返されます。
同様に、元のクエリにOPTION (MAXDOP 1)
を追加すると、統計情報が大幅に少なくなり、LOBの読み取りが大幅に少なくなります。
_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.
_
誰かが何が起こっているのか説明できますか?なぜ元の計画が壊滅的に悪化し、問題を回避する信頼できる方法があるのですか?
追加:
また、クエリを_INNER HASH JOIN
_に変更すると、DMVの結果が非常に小さいため、ある程度改善されます(ただし、3分以上かかります)。変わったに違いない。そのための統計
_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.
_
拡張イベントリングバッファーをいっぱいにして(DATALENGTH
のXML
は4,880,045バイトで、1,448イベントが含まれていました。)元のクエリの縮小バージョンをMAXDOP
ヒント。
_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
_
次の結果を与えた
_+-------------------------------------+------+----------+
| | 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 |
+-------------------------------------+------+----------+
_
Tempdbの割り当てには明確な違いがあり、_616
_ページが割り当てられ、割り当て解除されたことを示すより速いものと異なります。これは、XMLが変数に入力されるときに使用されるページ数と同じです。
スロープランの場合、これらのページ割り当て数は数百万になります。クエリの実行中に_dm_db_task_space_usage
_をポーリングすると、tempdb
のページの割り当てと割り当て解除が常に行われているように見え、一度に1,800〜3,000ページが割り当てられます。
パフォーマンスの違いの理由は、実行エンジンでのスカラー式の処理方法にあります。この場合、関心のある表現は次のとおりです。
[Expr1000] = CONVERT(xml,DM_XE_SESSION_TARGETS.[target_data],0)
この式ラベルは、スカラー計算演算子(シリアルプランのノード11、パラレルプランのノード13)によって定義されます。 Compute Scalar演算子は、それらが定義する式が 必ずしもそれらが出現する位置で評価されるとは限りません 可視実行プラン内で他の演算子(SQL Server 2005以降)と異なります。評価は、後のオペレーターが計算結果を要求するまで延期することができます。
現在のクエリでは、target_data
文字列は通常大きく、文字列からXML
への変換にコストがかかります。遅いプランでは、Expr1000
の結果を必要とする後の演算子が再バインドされるたびに、文字列からXML
への変換が実行されます。
再バインドは、相関パラメーター(外部参照)が変更されると、ネストされたループ結合の内側で発生します。 Expr1000
は、この実行プランのほとんどのネストされたループ結合の外部参照です。式は、複数のXMLリーダー、両方のストリーム集約、および起動フィルターによって複数回参照されます。 XML
のサイズに応じて、文字列がXML
に変換される回数は、数百万単位で簡単に数えられます。
以下のコールスタックは、target_data
文字列がXML
(ConvertStringToXMLForES
-ESがExpression Service)に変換される例を示しています。
起動フィルター
XMLリーダー(TVFストリーム内部)
Stream Aggregate
これらの演算子のいずれかが再バインドされるたびに文字列をXML
に変換すると、ネストされたループプランで観察されるパフォーマンスの違いがわかります。これは、並列処理が使用されているかどうかには関係ありません。 MAXDOP 1
ヒントが指定されているときに、オプティマイザがハッシュ結合を選択するのは、たまたまです。 MAXDOP 1, LOOP JOIN
が指定されている場合、デフォルトの並列プラン(オプティマイザがネストされたループを選択する)と同様に、パフォーマンスが低下します。
ハッシュ結合によってパフォーマンスがどの程度向上するかは、Expr1000
がオペレーターのビルド側とプローブ側のどちらに表示されるかによって異なります。次のクエリは、プローブ側の式を見つけます。
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_sessions s
INNER HASH JOIN sys.dm_xe_session_targets st 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';
ジョインのヒント(上記のINNER HASH JOIN
)も、FORCE ORDER
が指定された場合と同様に、クエリ全体の順序を強制するため、結合の記述順序を質問に示したバージョンと逆にしました。プローブ側にExpr1000
が表示されるようにするには、反転が必要です。実行計画の興味深い部分は次のとおりです。
プローブ側で定義された式により、値はキャッシュされます。
Expr1000
の評価は、最初のオペレーターが値(上記のスタックトレースの起動フィルター)を必要とするまで延期されますが、計算された値はキャッシュされ(CValHashCachedSwitch
)、XMLリーダーとストリーム集約による後の呼び出しで再利用されます。以下のスタックトレースは、XMLリーダーによって再利用されているキャッシュされた値の例を示しています。
Expr1000
の定義がハッシュ結合のビルド側で発生するように結合順序が強制される場合、状況は異なります。
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
INNER HASH 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'
ハッシュ結合は、一致のプローブを開始する前に、ビルド入力を完全に読み取ってハッシュテーブルを構築します。その結果、プランのプローブ側から処理されているスレッドごとの値だけでなく、all値を格納する必要があります。したがって、ハッシュ結合はtempdb
作業テーブルを使用してXML
データを格納し、後の演算子がExpr1000
の結果にアクセスするたびに、tempdb
への高額なトリップが必要になります。
次に、低速アクセスパスの詳細を示します。
マージ結合が強制されると、入力行がソートされ(ハッシュ結合へのビルド入力と同様にブロッキング操作)、データのサイズのためにtempdb
ソート最適化ワークテーブルを介した低速アクセスが必要となる同様の配置になります。
大きなデータアイテムを操作するプランは、実行プランからは明らかではないあらゆる種類の理由で問題になる可能性があります。 (正しい入力の式で)ハッシュ結合を使用することは良い解決策ではありません。ドキュメント化されていない内部動作に依存しており、来週も同じように動作するか、わずかに異なるクエリで動作することを保証していません。
メッセージは、XML
の操作は、今日最適化するのが難しいことです。シュレッドの前にXML
を変数または一時テーブルに書き込むことは、上記のどの方法よりも確実な回避策です。これを行う1つの方法は次のとおりです。
DECLARE @data xml =
CONVERT
(
xml,
(
SELECT TOP (1)
dxst.target_data
FROM sys.dm_xe_sessions AS dxs
JOIN sys.dm_xe_session_targets AS dxst ON
dxst.event_session_address = dxs.[address]
WHERE
dxs.name = N'system_health'
AND dxst.target_name = N'ring_buffer'
)
)
SELECT XEventData.XEvent.value('(data/value)[1]', 'varchar(max)')
FROM @data.nodes ('./RingBufferTarget/event[@name eq "xml_deadlock_report"]') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name', 'varchar(4000)') = 'xml_deadlock_report';
最後に、以下のコメントからマーティンの非常に素晴らしいグラフィックを追加したいと思います。
それは最初にここに投稿された私の記事のコードです:
http://www.sqlservercentral.com/articles/deadlock/65658/
コメントを読むと、発生しているパフォーマンスの問題がないいくつかの代替案が見つかります。1つは元のクエリの変更を使用し、もう1つは処理する前に変数を使用してXMLを保持します。より良い。 (ページ2の私のコメントを参照)DMVからのXMLは、ファイルターゲットのDMFからのXMLの解析と同様に処理が遅い場合があります。これは、多くの場合、最初にデータを一時テーブルに読み込んでから処理することでより適切に実行されます。 SQLでのXMLは、.NETやSQLCLRのようなものを使用する場合に比べて低速です。