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


34

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

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

平行

WHERE句を削除すると、3,782行を返す1秒未満で完了します。

同様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

シリアル

だから私の質問は

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

添加:

またINNER HASH JOIN、DMVの結果が非常に小さいため、クエリを変更してある程度改善します(ただし、3分以上かかります)が、Joinタイプ自体が原因であるとは思わず、他の何かが変更されていると思われます。そのための統計

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.

(そして計画)

(拡張されたイベントリングバッファを充填した後DATALENGTHXML4880045バイトであり、それは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クエリの実行中にポーリングを行うと、1,800〜3,000のページがtempdb一度に割り当てられ、常にページの割り当てと割り当て解除が行われているようです。


WHERE句をXQuery式に移動できます。高速化するためにロジックを削除する必要はありませんTargetData.nodes ('RingBufferTarget[1]/event[@name = "xml_deadlock_report"]')。そうは言っても、私はあなたが提起した質問に答えるのに十分なほどXML内部を知らない。
ジョンセイゲル

あなたのための@SQLPoolBoyのページングMartin ...彼は、ここコメントを読むことを提案しました。より効率的な提案があります(上記のコードのソース記事に基づいています)。
アーロンバートランド

回答:


36

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

[Expr1000] = CONVERT(xml,DM_XE_SESSION_TARGETS.[target_data],0)

この式ラベルは、Compute Scalarオペレーター(シリアルプランのノード11、パラレルプランのノード13)によって定義されます。Compute Scalar演算子は他の演算子(SQL Server 2005以降)と異なり、定義する式が表示される実行プランに表示される位置で必ずしも評価されるわけではありません。評価は、計算の結果が後のオペレーターによって要求されるまで延期できます。

現在のクエリでtarget_dataは、通常、文字列は大きく、文字列からXML高価なものに変換されます。遅いプランでXMLは、結果を必要とする後の演算子Expr1000がリバウンドされるたびに、文字列から変換への変換が実行されます。

相関パラメーター(外部参照)が変更されると、ネストされたループ結合の内側で再バインドが発生します。Expr1000この実行プランのほとんどのネストされたループ結合の外部参照です。式は、複数のXMLリーダー(ストリームアグリゲートと起動フィルター)によって複数回参照されます。のサイズに応じて、XML文字列が変換されるXML回数は数百万単位で簡単に数えられます。

以下の呼び出しスタックは、target_data変換される文字列の例を示していますXMLConvertStringToXMLForES-ESはExpression Service):

起動フィルター

起動フィルター呼び出しスタック

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

TVFストリームコールスタック

ストリーム集約

ストリーム集約呼び出しスタック

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、プローブ側に表示されるようにするために必要です。実行計画の興味深い部分は次のとおりです。

ヒント1

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

ハッシュキャッシュ

の評価は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'

ハッシュ2

ハッシュ結合は、ビルド入力を完全に読み取り、一致のプローブを開始する前にハッシュテーブルを構築します。その結果、プランのプローブ側から処理されているスレッドごとの値だけでなく、すべての値を保存する必要があります。そのため、ハッシュ結合では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';

最後に、以下のコメントからMartinの非常に素晴らしいグラフィックを追加したいだけです。

マーティンのグラフィック


素晴らしい説明、ありがとう。計算スカラーに関する記事も読んでいましたが、ここでは2つと2つをまとめませんでした。
マーティンスミス

3
昨日のプロファイリングの試みで何かを台無しにしたに違いありません(たぶん、遅いトレースと速いトレースを混乱させました!)。今日はやり直しましたが、もちろん、あなたが既に言ったことを示しています。
マーティンスミス

2
はい、スクリーンショットはVisual Studio 2012プロファイラーからのコールツリービューレポートです。メソッド名は、出力ではより明確に見えますが、表示されるなどの不思議な文字列はありません@@IEAAXPEA_K
マーティンスミス

10

これは、元々ここに投稿された私の記事のコードです。

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

コメントを読むと、発生しているパフォーマンスの問題がないいくつかの選択肢があります。1つは元のクエリの変更を使用し、もう1つは処理する前にXMLを保持する変数を使用しますより良い。(2ページの私のコメントを参照してください)DMVからのXMLは、ファイルターゲットのDMFからXMLを解析するのと同様に、処理に時間がかかる場合があります。SQLのXMLは、.NETやSQLCLRのようなものを使用するのに比べて遅いです。


1
ありがとう!それはトリックをしました。変数なしで600ミリ秒と6341の読み取りが行われ、変数303 ms3249 lob reads。2012年には、and target_name='ring_buffer'現在2つのターゲットがあるように見えるため、そのバージョンにも追加する必要がありました。しかし、私はまだ20分間のバージョンでそれが何をしているのかという精神的なイメージを得ようとしています。
マーティンスミス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.