web-dev-qa-db-ja.com

XMLリーダーを使用した計画の最適化

ここからのクエリ を実行して、デフォルトの拡張イベントセッションからデッドロックイベントを引き出します

_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.
_

スロープランXML

Parallel

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.
_

高速プランXML

Serial

だから私の質問は

誰かが何が起こっているのか説明できますか?なぜ元の計画が壊滅的に悪化し、問題を回避する信頼できる方法があるのですか?

追加:

また、クエリを_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.
_

(および計画)

拡張イベントリングバッファーをいっぱいにして(DATALENGTHXMLは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ページが割り当てられます。

35
Martin Smith

パフォーマンスの違いの理由は、実行エンジンでのスカラー式の処理方法にあります。この場合、関心のある表現は次のとおりです。

[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文字列がXMLConvertStringToXMLForES-ESがExpression Service)に変換される例を示しています。

起動フィルター

Start-up Filter call stack

XMLリーダー(TVFストリーム内部)

TVF Stream call stack

Stream Aggregate

Stream Aggregate call stack

これらの演算子のいずれかが再バインドされるたびに文字列を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が表示されるようにするには、反転が必要です。実行計画の興味深い部分は次のとおりです。

hint 1

プローブ側で定義された式により、値はキャッシュされます。

Hash Cache

Expr1000の評価は、最初のオペレーターが値(上記のスタックトレースの起動フィルター)を必要とするまで延期されますが、計算された値はキャッシュされ(CValHashCachedSwitch)、XMLリーダーとストリーム集約による後の呼び出しで再利用されます。以下のスタックトレースは、XMLリーダーによって再利用されているキャッシュされた値の例を示しています。

Cache reuse

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'

Hash 2

ハッシュ結合は、一致のプローブを開始する前に、ビルド入力を完全に読み取ってハッシュテーブルを構築します。その結果、プランのプローブ側から処理されているスレッドごとの値だけでなく、all値を格納する必要があります。したがって、ハッシュ結合はtempdb作業テーブルを使用してXMLデータを格納し、後の演算子がExpr1000の結果にアクセスするたびに、tempdbへの高額なトリップが必要になります。

Slow access

次に、低速アクセスパスの詳細を示します。

Slow details

マージ結合が強制されると、入力行がソートされ(ハッシュ結合へのビルド入力と同様にブロッキング操作)、データのサイズのために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';

最後に、以下のコメントからマーティンの非常に素晴らしいグラフィックを追加したいと思います。

Martin's graphic

37
Paul White 9

それは最初にここに投稿された私の記事のコードです:

http://www.sqlservercentral.com/articles/deadlock/65658/

コメントを読むと、発生しているパフォーマンスの問題がないいくつかの代替案が見つかります。1つは元のクエリの変更を使用し、もう1つは処理する前に変数を使用してXMLを保持します。より良い。 (ページ2の私のコメントを参照)DMVからのXMLは、ファイルターゲットのDMFからのXMLの解析と同様に処理が遅い場合があります。これは、多くの場合、最初にデータを一時テーブルに読み込んでから処理することでより適切に実行されます。 SQLでのXMLは、.NETやSQLCLRのようなものを使用する場合に比べて低速です。

10