web-dev-qa-db-ja.com

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

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

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

テストシナリオ

まず、テストテーブルを作成し、100.000行で埋めます。乱数(0から1000)は、乱数ごとに〜100行になるはずです。この数値はvarchar colに入れられ、値としてXMLに入れられます。

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

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

さて、何が本当に私を驚かせたのでしょう! -_.nodes_とフルパスは以前(9秒)よりもはるかに遅くなりますが、.exist()は半分になります約0.10秒までフルパスで1秒。 (一方で.nodes() withshort pathの方が良いですが、それでも.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
_

編集2より大きなXMLでテストする

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

XMLインデックスを作成すると、mdfファイルが最大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 !!!
_
32
Shnugo

ここでは多くのことが起こっているので、これがどこにつながるかを確認する必要があります。

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

nodes()exist()の比較は、XMLで1つの行の一致する要素が複数ある場合に同じ結果を返さないため、公平ではありません。 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()またはexist()を使用しても違いはありません。これは、SQL Serverが、インデックスを使用しない2つのバージョンでほぼ同じプランを作成し、まったく同じプランを作成するためです。インデックスを使用する場合。これは、SQL Server 2012とSQL Server 2014の両方に当てはまります。

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

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

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

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

答えは、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._ごとに1つの行があるため、当然ながら、格納されているXMLのサイズに応じてテーブルが非常に大きくなる可能性があります。プライマリXMLインデックスを配置すると、SQL Serverは内部テーブルのプライマリキーを使用して、ベーステーブルの各行のXMLノードと値を見つけることができます。

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

From CREATE XML INDEX(Transact-SQL)


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


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

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

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

あなたの質問

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

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

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

テーブルに作成されたXMLインデックスがある場合、SQL Serverは常にそのインデックス(内部テーブル)を使用してデータを取得します。その決定は、オプティマイザが何が高速で何が高速でないかについて発言する前に行われます。オプティマイザへの入力は、内部テーブルを使用するように書き直され、その後、通常のクエリと同様に最善を尽くすのはオプティマイザに任されます。インデックスを使用しない場合、代わりに使用されるテーブル値関数がいくつかあります。肝心なのは、テストなしでは何が速くなるかわからないということです。

どのようにして悪影響を回避できますか?

テスト中

33
Mikael Eriksson