web-dev-qa-db-ja.com

大きなテーブルに対するSQL Server 2008R2 XML列インデックス戦略?

SQL Server 2008 R2と、非常に大きなテーブルのいくつかのXML列を使用しています。 SQL Serverのバージョンが2012未満であるため、 Selective XML Indexes を使用できないことを理解しています。XML列にインデックスを付けるという考えにまったく新しいことをしています。

目標

XMLにクエリを実行する必要があるシナリオが2つあります。

  1. 1つのケースでは、300万行のテーブル全体にネストされた値が存在するかどうかを照会する必要があります
    • 例えば。 columnName.exist('Parent[1]/Child[1]/DifferentChild[1]') = 1'
  2. 別のケースでは、470万行のテーブルで、XMLから2つの値を抽出して2つの列に一致させる必要があります。
    • 例えば。 SELECT columnName.value('Parent[1]/FilePath[1]', 'nvarchar(max)') as FilePath, columnName.value('Parent[1]/FileName[1]', 'nvarchar(255)') as FileName

質問

  • ここでのインデックスのコストとクエリの固定タイプを考えると、最大の影響を与えるために適用できるインデックスは何ですか?また、知っておくべき落とし穴はありますか?
4
SeanKilleen

SQL ServerでXMLクエリを調整する方法はいくつかあります。プロパティのプロモーションは良いものですが、私は次のものも定期的に使用しています。

XMLインデックス

XMLインデックスはXMLクエリのパフォーマンスを変換できますが、コストがかかります。 SQL Server 2012より前のバージョンには、プライマリXMLインデックスとセカンダリXMLインデックスの2つのタイプがあります。常にプライマリXMLインデックスが必要であり、オプションでPATH、PROPERTY、またはVALUEインデックスを追加して、目的が少し異なるようにすることができます。特定のクエリの場合、セカンダリPATHインデックスを使用すると、以下の簡単なリグでステップ変更のパフォーマンスが向上します。例:

CREATE PRIMARY XML INDEX xmlidx_largeTable ON dbo.largeTable ( yourXML )
GO

CREATE XML INDEX xpthidx_largeTable ON dbo.largeTable ( yourXML )
USING XML INDEX xmlidx_largeTable FOR PATH
GO

今度はコストです。 XMLインデックス(選択的XMLインデックスの前)は、ストレージに大きな影響を与えます。テーブルのサイズが最大5倍になるのを見てきました。以下の私のテストリグでは、300万行と非常に単純なXMLを持つテーブルは、プライマリXMLインデックスで0.7GBから2GBになり、次にPATHセカンダリインデックスで2.7GBになります。 SQL Server 2012以降の選択的XMLインデックスは、これを大幅に改善できます。

ベストプラクティス構文

左から右にドリルダウンするXMLのレベルが複数ある場合は、 CROSS APPLY を使用します。以下の私のリグでCROSS APPLYの使用を参照してください。また、親軸(..)を使用してドリルアップしないでください。これは、特に ここ に示すように、XMLのより大きな部分でパフォーマンスの問題を引き起こす可能性があります。

また、型なしのXMLでは常にtext()アクセサーを使用します。たとえば、

SELECT
    p.c.value('(FilePath/text())[1]', 'nvarchar(max)') AS FilePath,
    p.c.value('(FileName/text())[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t
    CROSS APPLY t.yourXML.nodes('Parent') p(c)

これは ここで言及されており 、この手法によりパフォーマンスが最大15%向上することがわかりました。 YMMV。序数([1])を式の最後に移動する方が効率的で、構文的にはParent[1]/FilePath[1]/SomeOtherElement[1]と同等です。

XMLスキーマコレクション

これらはパフォーマンスの向上をもたらす傾向はありませんが、制約のようにXMLに特定の構造を強制するので、良い習慣です。

全文索引

フルテキストインデックスとXMLを組み合わせて、良い結果が得られることもあります(例: here )。基準がないように思われるため、この例ではおそらく適切ではありません。

テストリグ

私の単純なテストリグでは、300万行の単純なテーブルを作成し、各行に単純なXMLを入れています。次に、構文とXMLインデックスのさまざまな組み合わせを試して、違いを確認します。

USE tempdb
GO

SET NOCOUNT ON
GO

------------------------------------------------------------------------------------------------
-- Setup START
------------------------------------------------------------------------------------------------

-- Create a large table 
IF OBJECT_ID('dbo.largeTable') IS NOT NULL DROP TABLE dbo.largeTable
CREATE TABLE dbo.largeTable ( 
    rowId INT IDENTITY PRIMARY KEY, 
    someData UNIQUEIDENTIFIER DEFAULT NEWID(), 
    dateAdded DATETIME DEFAULT GETDATE(), 
    addedBy VARCHAR(30) DEFAULT SUSER_NAME(),
    yourXML XML,
    ts ROWVERSION
    )
GO


-- Add 3 million rows to the table; with simple piece of XML in each row
;WITH cte AS (
SELECT TOP 1000000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT INTO dbo.largeTable ( someData, yourXML )
SELECT NEWID(),
    (
    SELECT
        rn AS Child,
        'DifferentChild' + CAST( CASE WHEN rn % 9999 = 0 THEN rn % 33 ELSE NULL END AS VARCHAR(10) ) AS "Child/DifferentChild",
        'FilePath' + CAST( rn AS VARCHAR(10) ) AS FilePath,
        'FileName' + CAST( rn AS VARCHAR(10) ) AS [FileName]
    FOR XML PATH('Parent'), TYPE
    )
FROM cte
GO 3

-- Setup END
------------------------------------------------------------------------------------------------



------------------------------------------------------------------------------------------------
-- Queries START
------------------------------------------------------------------------------------------------

-- Query 1:
SELECT *
FROM dbo.largeTable t
WHERE t.yourXML.exist('Parent[1]/Child[1]/DifferentChild[1]') = 1

-- Improve query
SELECT *
FROM dbo.largeTable t
WHERE t.yourXML.exist('(Parent/Child/DifferentChild)[1]') = 1
GO


-- Query 2:
SELECT
    yourXML.value('Parent[1]/FilePath[1]', 'nvarchar(max)') AS FilePath,
    yourXML.value('Parent[1]/FileName[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t


SELECT
    yourXML.value('(Parent/FilePath/text())[1]', 'nvarchar(max)') AS FilePath,
    yourXML.value('(Parent/FileName/text())[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t


SELECT
    p.c.value('(FilePath/text())[1]', 'nvarchar(max)') AS FilePath,
    p.c.value('(FileName/text())[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t
    CROSS APPLY t.yourXML.nodes('Parent') p(c)
GO

-- Queries END
------------------------------------------------------------------------------------------------


CREATE PRIMARY XML INDEX xmlidx_largeTable ON dbo.largeTable ( yourXML )
GO


------------------------------------------------------------------------------------------------
-- Queries START
------------------------------------------------------------------------------------------------

-- Query 1:
SELECT *
FROM dbo.largeTable t
WHERE t.yourXML.exist('Parent[1]/Child[1]/DifferentChild[1]') = 1

-- Improve query
SELECT *
FROM dbo.largeTable t
WHERE t.yourXML.exist('(Parent/Child/DifferentChild)[1]') = 1
GO


-- Query 2:
SELECT
    yourXML.value('Parent[1]/FilePath[1]', 'nvarchar(max)') AS FilePath,
    yourXML.value('Parent[1]/FileName[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t


SELECT
    yourXML.value('(Parent/FilePath/text())[1]', 'nvarchar(max)') AS FilePath,
    yourXML.value('(Parent/FileName/text())[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t


SELECT
    p.c.value('(FilePath/text())[1]', 'nvarchar(max)') AS FilePath,
    p.c.value('(FileName/text())[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t
    CROSS APPLY t.yourXML.nodes('Parent') p(c)
GO

-- Queries END
------------------------------------------------------------------------------------------------


CREATE XML INDEX xpthidx_largeTable ON dbo.largeTable ( yourXML )
USING XML INDEX xmlidx_largeTable FOR PATH
GO



------------------------------------------------------------------------------------------------
-- Queries START
------------------------------------------------------------------------------------------------

-- Query 1:
SELECT *
FROM dbo.largeTable t
WHERE t.yourXML.exist('Parent[1]/Child[1]/DifferentChild[1]') = 1

-- Improve query
SELECT *
FROM dbo.largeTable t
WHERE t.yourXML.exist('(Parent/Child/DifferentChild)[1]') = 1
GO


-- Query 2:
SELECT
    yourXML.value('Parent[1]/FilePath[1]', 'nvarchar(max)') AS FilePath,
    yourXML.value('Parent[1]/FileName[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t


SELECT
    yourXML.value('(Parent/FilePath/text())[1]', 'nvarchar(max)') AS FilePath,
    yourXML.value('(Parent/FileName/text())[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t


SELECT
    p.c.value('(FilePath/text())[1]', 'nvarchar(max)') AS FilePath,
    p.c.value('(FileName/text())[1]', 'nvarchar(255)') AS [FileName]
FROM dbo.largeTable t
    CROSS APPLY t.yourXML.nodes('Parent') p(c)
GO

-- Queries END
------------------------------------------------------------------------------------------------

私の結果:

enter image description here

つまり、要約すると、XMLクエリを適切な機能と組み合わせて使用​​することで段階的なパフォーマンスを得ることができますが、ストレージコストは非常に高くなります。

推奨読書

SQL Server 2005のXMLデータ型のパフォーマンスの最適化
http://msdn.Microsoft.com/en-us/library/ms345118.aspx

SQL Server 2005のXMLインデックス
http://msdn.Microsoft.com/en-us/library/ms345121(SQL.90).aspx

3
wBob

Microsoft SQL Server 2005のXMLベストプラクティス のプロパティプロモーションを確認する必要があります。

必要な値を抽出するユーザー定義のスカラー値関数を作成し、その関数をテーブルの計算列として使用します。好きな列を永続化したり、列に通常のインデックスを作成したりできます。インデックスを作成するために列を永続化する必要はありません。

3
Mikael Eriksson