XMLインデックスを使用した非常に奇妙なパフォーマンス


32

私の質問はこれに基づいています:https : //stackoverflow.com/q/35575990/5089204

そこで答えを出すために、次のテストシナリオを行いました。

テストシナリオ

最初にテストテーブルを作成し、100.000行を入力します。乱数(0から1000)は、乱数ごとに最大100行になるはずです。この番号はvarchar colに入れられ、XMLの値として使用されます。

次に、OPのような呼び出しを行います。.exist()および.nodes()を使用して、2番目の利点はわずかですが、両方とも5〜6秒かかります。実際に、呼び出しを2回行います。2回目はスワップされた順序で、検索パラメーターをわずかに変更し、フルパスではなく「// item」を使用して、キャッシュされた結果またはプランによる誤検知を回避します。

次に、XMLインデックスを作成し、同じ呼び出しを行います

今-本当に驚いたことは何ですか!- .nodesとの完全なパスがはるかに遅い(9秒)前よりもなく.exist()して、半秒までであるフルパスもダウン約0.10秒です。(一方.nodes()短いパスの方が優れていますが、それでもはるかに遅れています.exist()

質問:

私自身のテストをまとめると、XMLインデックスはデータベースを極端に破壊する可能性があります。それらは物事を非常に高速化できますが(s。edit 2)、クエリも遅くなります。それらがどのように機能するかを理解したいのですが... XMLインデックスを作成する必要があるのはいつですか?.nodes()インデックスを使用すると、インデックスを使用しない場合よりも悪くなるのはなぜですか?否定的な影響をどのように回避できますか?

CREATE TABLE #testTbl(ID INT IDENTITY PRIMARY KEY, SomeData VARCHAR(100),XmlColumn XML);
GO

DECLARE @RndNumber VARCHAR(100)=(SELECT CAST(CAST(RAND()*1000 AS INT) AS VARCHAR(100)));

INSERT INTO #testTbl VALUES('Data_' + @RndNumber,
'<error application="application" host="host" type="exception" message="message" >
  <serverVariables>
    <item name="name1">
      <value string="text" />
    </item>
    <item name="name2">
      <value string="text2" />
    </item>
    <item name="name3">
      <value string="text3" />
    </item>
    <item name="name4">
      <value string="text4" />
    </item>
    <item name="name5">
      <value string="My test ' +  @RndNumber + '" />
    </item>
    <item name="name6">
      <value string="text6" />
    </item>
    <item name="name7">
      <value string="text7" />
    </item>
  </serverVariables>
</error>');

GO 100000

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesFullPath_no_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistFullPath_no_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('//item[@name="name5" and value/@string="My test 500"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistShortPath_no_index;
GO

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('//item[@name="name5" and value/@string="My test 500"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesShortPath_no_index;
GO

CREATE PRIMARY XML INDEX PXML_test_XmlColum1 ON #testTbl(XmlColumn);
CREATE XML INDEX IXML_test_XmlColumn2 ON #testTbl(XmlColumn) USING XML INDEX PXML_test_XmlColum1 FOR PATH;
GO

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesFullPath_with_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistFullPath_with_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('//item[@name="name5" and value/@string="My test 500"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistShortPath_with_index;
GO

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('//item[@name="name5" and value/@string="My test 500"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesShortPath_with_index;
GO

DROP TABLE #testTbl;

編集1-結果

これは、中規模のラップトップにローカルにインストールされたSQL Server 2012の結果の1つです。このテストNodesFullPath_with_indexでは、インデックスがない場合よりも遅いものの、非常に否定的な影響を再現できませんでした...

NodesFullPath_no_index    6.067
ExistFullPath_no_index    6.223
ExistShortPath_no_index   8.373
NodesShortPath_no_index   6.733

NodesFullPath_with_index  7.247
ExistFullPath_with_index  0.217
ExistShortPath_with_index 0.500
NodesShortPath_with_index 2.410

より大きなXMLでのEDIT 2テスト

TTの提案によると、上記のXMLを使用しましたが、item-nodesをコピーして約450個のアイテムに到達しました。ヒットノードをXML内で非常に高くしました(.exist()最初のヒットで停止するが、.nodes()続行すると思うので)

XML-indexを作成するとmdf-fileが〜21GBに爆発し、〜18GBはインデックスに属しているようです(!!!)

NodesFullPath_no_index    3min44
ExistFullPath_no_index    3min39
ExistShortPath_no_index   3min49
NodesShortPath_no_index   4min00

NodesFullPath_with_index  8min20
ExistFullPath_with_index  8,5 seconds !!!
ExistShortPath_with_index 1min21
NodesShortPath_with_index 13min41 !!!

回答:


33

ここでは多くのことが行われているので、これがどこにつながるかを確認するだけです。

まず、SQL Server 2012とSQL Server 2014のタイミングの違いは、SQL Server 2014の新しいカーディナリティエスティメータによるものです。SQLServer 2014のトレースフラグを使用して古いエスティメータを強制すると、同じタイミングが表示されます。 SQL Server 2012と同様のSQL Server 2014の特性。

nodes()vsの比較exist()は、1行のXMLに一致する要素が複数ある場合に同じ結果を返さないため、公平ではありません。exist()関係なくベーステーブルから1行を返しますが、ベーステーブルの各行に対してnodes()複数の行が返される可能性があります。
データはわかっていますが、SQL Serverはそのことを考慮しておらず、それを考慮したクエリプランを作成する必要があります。

nodes()クエリをクエリと同等にするために、exist()次のようなことができます。

SELECT testTbl.*
FROM testTbl
WHERE EXISTS (
             SELECT *
             FROM XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b)
             )

このようなクエリでは、nodes()or exist()を使用しても、インデックスを使用していない2つのバージョンとインデックスを使用した場合とまったく同じプランでSQL Serverがほぼ同じプランを作成するため、違いはありません。これは、SQL Server 2012とSQL Server 2014の両方に当てはまります。

SQL Server 2012の私にとって、XMLインデックスのないクエリは、nodes()上記のクエリの修正バージョンを使用して6秒かかります。フルパスを使用する場合とショートパスを使用する場合に違いはありません。XMLインデックスを配置すると、フルパスバージョンが最速で5ミリ秒かかり、ショートパスを使用すると約500ミリ秒かかります。クエリプランを調べると、違いがある理由がわかりますが、短いバージョンでは、短いパスを使用すると、SQL Serverは短いパスのインデックスをシークし(範囲シークを使用like)、700,000行を返してから行を破棄します値が一致しません。フルパスを使用する場合、SQL Serverはノードの値と共にパス式を直接使用してシークを実行し、処理するために最初から105行のみを返します。

SQL Server 2014と新しい基数推定器を使用すると、XMLインデックスを使用する場合、これらのクエリに違いはありません。インデックスを使用しなくても、クエリは同じ時間かかりますが、15秒です。新しいものを使用する場合、ここでは明らかに改善されません。

クエリを同等のものに変更したため、実際にあなたの質問が何であるかを完全に失ったかどうかはわかりませんが、今はそうだと思います。

nodes()インデックスが使用されていない場合に、XMLインデックスを使用したクエリ(元のバージョン)が大幅に遅いのはなぜですか?

答えは、SQL Serverクエリプランオプティマイザーが何か悪いことをして、それがスプールオペレーターを導入しているということです。理由はわかりませんが、SQL Server 2014の新しいカーディナリティエスティメータにはもうないというのが朗報です。
インデックスが設定されていない場合、使用されるカーディナリティエスティメータに関係なく、クエリは約7秒かかります。インデックスを使用すると、古い推定器(SQL Server 2012)では15秒、新しい推定器(SQL Server 2014)では約2秒かかります。

注:上記の結果は、テストデータで有効です。XMLのサイズ、形状、または形式を変更したかどうかを判断するためのまったく異なるストーリーが存在する場合があります。実際にテーブルにあるデータをテストせずに確実に知る方法はありません。

XMLインデックスの仕組み

SQL ServerのXMLインデックスは、内部テーブルとして実装されます。プライマリXMLインデックスは、ベーステーブルのプライマリキーとノードIDカラムの合計12カラムでテーブルを作成します。element/node/attribute etc.格納されるXMLのサイズに応じてテーブルが非常に大きくなるように、1行に1行が含まれます。プライマリXMLインデックスを配置すると、SQL Serverは内部テーブルのプライマリキーを使用して、ベーステーブルの各行のXMLノードと値を特定できます。

セカンダリXMLインデックスには3つのタイプがあります。セカンダリXMLインデックスを作成すると、内部テーブルに非クラスター化インデックスが作成されます。作成するセカンダリインデックスの種類に応じて、列と列の順序は異なります。

XML INDEX(Transact-SQLの)を作成します

VALUE
キー列がプライマリXMLインデックスの(ノード値とパス)である列にセカンダリXMLインデックスを作成します。

PATH
プライマリXMLインデックスのパス値とノード値に基づいて構築された列にセカンダリXMLインデックスを作成します。PATHセカンダリインデックスでは、パスとノードの値は、パスを検索するときに効率的なシークを可能にするキー列です。

PROPERTY
プライマリXMLインデックスの列(PK、パス、ノード値)にセカンダリXMLインデックスを作成します。ここで、PKはベーステーブルのプライマリキーです。

したがって、PATHインデックスを作成するとき、そのインデックスの最初の列はパス式であり、2番目の列はそのノードの値です。実際には、パスは一種の圧縮形式で保存され、逆にされます。それが逆に保存されていることが、短いパス式を使用した検索で有用な理由です。あなたのショートパスケースでは、検索//item/value/@string//item/@nameおよび//item。パスは列に逆に格納されるため、SQL Server は、パスが逆になっているlike = '€€€€€€%場所で範囲シークを使用できます€€€€€€。完全なパスを使用する場合、パスlike全体が列でエンコードされ、値はシーク述部でも使用できるため、使用する理由はありません。

あなたの質問

XMLインデックスを作成する必要があるのはいつですか?

これまでの最後の手段として。where句でフィルター処理するためにXML内の値を使用する必要がないように、データベースを設計することをお勧めします。事前にその必要があることがわかっている場合は、プロパティプロモーションを使用して、必要に応じてインデックスを作成できる計算列を作成できます。SQL Server 2012 SP1以降では、選択可能なXMLインデックスも利用できます。舞台裏での動作は、通常のXMLインデックスの場合とほとんど同じです。インデックス定義でパス式を指定し、一致するノードのみがインデックス付けされます。そうすれば、多くのスペースを節約できます。

インデックス付きの.nodes()は、なしよりも悪いのはなぜですか?

テーブルにXMLインデックスが作成されている場合、SQL Serverは常にそのインデックス(内部テーブル)を使用してデータを取得します。この決定は、オプティマイザーが高速なものとそうでないものについて発言する前に行われます。オプティマイザーへの入力は書き換えられ、内部テーブルを使用します。その後、通常のクエリと同様に最適化を行うのはオプティマイザー次第です。インデックスが使用されない場合、代わりに使用されるテーブル値関数がいくつかあります。一番下の行は、テストせずに高速になるものを伝えることができないということです。

マイナスの影響をどのように回避できますか?

テスト中


2
.nodes()との違いについてのあなたのアイデア.exist()は説得力があります。また、インデックスfull path searchが速いという事実は理解しやすいようです。これが意味するだろう:あなたはXMLインデックスを作成する場合は、あなたがしなければならない、常に任意の汎用のXPath(負の影響を意識する//か、*または..または[filter]または何もない単なるXpathは...)。実際には、フルパスのみを使用する必要があります-非常に素晴らしいバックドロー
...-Shnugo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.