XMLフィールドが存在すると、ほとんどのテーブルデータがLOB_DATAページに配置されます(実際、テーブルページの約90%がLOB_DATAです)。
テーブルにXML列があるだけでは、その効果はありません。これは、XMLの存在であるデータ、特定の条件下では、行のデータの一部がLOB_DATAページに、行をオフに記憶させます。そして、1つ(または複数の;-)が当たり前だと主張するかもしれませんが、XML
列は実際にXMLデータがあることを意味しますが、XMLデータが行外に格納される必要があることは保証されません: XMLデータではなく、小さなドキュメント(最大8000バイト)が行に収まり、LOB_DATAページに移動しない場合があります。
LOB_DATAページはサイズが大きいだけでなく、テーブルに多くのLOB_DATAページがある場合にSQL Serverがクラスター化インデックスを効果的にスキャンできないため、低速スキャンを引き起こす可能性があると考えるのは正しいですか?
スキャンとは、すべての行を調べることです。もちろん、列のサブセットを選択した場合でも、データページが読み取られると、行内のすべてのデータが読み取られます。LOBデータとの違いは、その列を選択しないと、行外のデータが読み取られないことです。したがって、SQL Serverがこのクラスター化インデックスを正確にテストしなかった(または半分をテストした)ため、このクラスター化インデックスをどれだけ効率的にスキャンできるかについて結論を出すことは、本当に公平ではありません。XML列を含むすべての列を選択しました。前述したように、そこにほとんどのデータがあります。
そのため、SELECT TOP 1000 *
テストでは、一連の8kデータページをすべて連続して読み取るだけでなく、各行ごとに他の場所にジャンプすることを既に知っています。そのLOBデータの正確な構造は、その大きさによって異なる場合があります。ここに示されている調査(Varchar、Varbinaryなどの(MAX)タイプのLOBポインターのサイズは?)に基づいて、 2つのタイプの行外LOB割り当てがあります。
- インラインルート-スペースが許す8001〜40,000(実際には42,000)バイトのデータの場合、LOBページを直接指す1〜5個のポインター(24〜72バイト)IN ROWがあります。
- TEXT_TREE-42,000バイトを超えるデータの場合、または1から5個のポインターが行内に収まらない場合、LOBページへのポインターのリストの開始ページへの24バイトのポインターがあります(つまり、 text_tree」ページ)。
これらの2つの状況の1つは、8000バイトを超える、または行に収まらないLOBデータを取得するたびに発生します。PasteBin.com(LOBの割り当てと読み取りをテストするT-SQLスクリプト)にテストスクリプトを投稿しました。これは、3種類のLOB割り当て(データのサイズに基づく)と、それぞれが論理的および物理的な読み取り。あなたの場合、XMLデータが実際に1行あたり42,000バイト未満である場合、最も効率の悪いTEXT_TREE構造には含まれない(または非常に少ない)はずです。
SQL Serverがクラスター化インデックスをスキャンする速度をテストする場合は、そのXML列を含まないSELECT TOP 1000
1つ以上の列を指定してください。結果にどのような影響がありますか?かなり高速になるはずです。
そのようなテーブル構造/データパターンを持つことは合理的であると考えられていますか?
実際のテーブル構造とデータパターンの不完全な説明があるため、それらの欠落している詳細が何であるかに応じて、どの答えも最適ではない可能性があります。それを念頭に置いて、テーブル構造やデータパターンについて明らかに不合理なものは何もないと思います。
(ac#アプリで)XMLを20KBから〜2.5KBに圧縮し、VARBINARY列に格納して、LOBデータページの使用を防ぐことができます。これにより、テストでSELECTが20倍高速化されます。
これにより、すべての列、またはXMLデータ(現在はVARBINARY
)のみを選択する速度が速くなりましたが、実際には「XML」データを選択しないクエリが破損します。他の列に約50バイト、FILLFACTOR
100のバイトがあると仮定すると、次のようになります。
圧縮なし:15kのXML
データには2つのLOB_DATAページが必要であり、インラインルートには2つのポインターが必要です。最初のポインターは24バイトで、2番目は12バイトで、合計36バイトがXMLデータの行に格納されます。行の合計サイズは86バイトであり、これらの行のうち約93行を8060バイトのデータページに収めることができます。したがって、100万行には10,753データページが必要です。
カスタム圧縮:2.5kのVARBINARY
データが行に収まります。行の合計サイズは2610(2.5 * 1024 = 2560)バイトであり、これらの行のうち3行のみを8060バイトのデータページに収めることができます。したがって、100万行には333,334データページが必要です。
エルゴ、カスタム圧縮を実装すると、クラスター化インデックスのデータページが30 倍に増加します。つまり、クラスター化インデックススキャンを使用するすべてのクエリに約322,500が追加されましたデータページが読み込まれます。このタイプの圧縮を行うことの追加の影響については、以下の詳細なセクションを参照してください。
のパフォーマンスに基づいてリファクタリングを行うことには注意します SELECT TOP 1000 *
。これは、アプリケーションが発行するクエリである可能性は低いため、不必要な最適化の唯一の基盤として使用しないでください。
より詳細な情報と試すべきテストについては、以下のセクションを参照してください。
この質問に明確な答えを与えることはできませんが、少なくともある程度の進歩を遂げ、正確な問題の解明に近づけるために追加の研究を提案することができます(証拠に基づいて)。
私たちが知っていること:
- テーブルには約100万行あります
- テーブルサイズは約15 GBです
- 表には、いずれかが含まれ
XML
、列と種類のいくつかの他の列を:INT
、BIGINT
、UNIQUEIDENTIFIER
、「など」
XML
列「サイズ」は、平均で約15k
- 実行後
DBCC DROPCLEANBUFFERS
、次のクエリが完了するまでに20〜25秒かかります。SELECT TOP 1000 * FROM TABLE
- クラスター化インデックスがスキャンされています
- クラスタ化インデックスの断片化は0%に近い
私たちが知っていると思うこと:
- これらのクエリ以外のディスクアクティビティはありません。本気ですか?他のユーザークエリがない場合でも、バックグラウンド操作が行われていますか?IOの一部を使用している可能性のある同じマシンで実行されているSQL Serverの外部にプロセスがありますか?ないかもしれませんが、提供された情報だけに基づいて明確ではありません。
- 15 MBのXMLデータが返されています。この数値は何に基づいていますか?1000行×1行あたり15kのXMLデータの平均から導き出された推定?または、そのクエリで受け取ったもののプログラムによる集計ですか?それが単なる推定である場合、XMLデータの分布は単純な平均によって暗示される方法でさえ均一ではないかもしれないので、私はそれに頼りません。
XML圧縮が役立つ場合があります。.NETで圧縮をどの程度正確に行いますか?GZipStreamまたはDeflateStreamクラス経由?これはゼロコストオプションではありません。確かにデータの一部を大幅に圧縮しますが、毎回データを圧縮/解凍するための追加プロセスが必要になるため、より多くのCPUも必要になります。この計画では、次の機能も完全に削除されます。
- 経由して、クエリXMLデータ
.nodes
、.value
、.query
、および.modify
XML機能。
XMLデータにインデックスを付けます。
(XMLは「非常に冗長」であると述べたので)覚えておいてください。したがって、GZip(またはその他)を介した圧縮から得られる利益は、要素および/または属性値を圧縮することによってのみ発見されます。上記の機能。XML
であると述べたため、要素と属性名をディクショナリに保存し、各アイテムに整数インデックスIDを割り当て、その整数IDを使用することでデータ型が既に最適化されていることにドキュメント全体で使用します(したがって、使用ごとにフルネームを繰り返したり、要素の終了タグとして繰り返したりすることはありません)。また、実際のデータから余分な空白が削除されます。これが、抽出されたXMLドキュメントが元の構造を保持せず、空の要素が次のように入力されたとして<element />
も抽出される理由です。<element></element>
また、XMLデータを圧縮してVARBINARY(MAX)
結果を保存しても、LOBアクセスが排除されるわけではなく、単に削減されることに注意してください。行の残りのデータのサイズによっては、圧縮された値が行内に収まる場合や、LOBページが必要な場合があります。
その情報は、有用ではありますが、ほとんど十分ではありません。クエリのパフォーマンスに影響する要因は多数あるため、何が起こっているのかをより詳細に把握する必要があります。
わからないが必要なこと:
- なぜパフォーマンスが
SELECT *
重要なのですか?これは、コードで使用するパターンですか。もしそうなら、なぜですか?
- XML列のみを選択した場合のパフォーマンスはどうですか?次のことを行う場合の統計とタイミングはどうなります
SELECT TOP 1000 XmlColumn FROM TABLE;
か?
これらの1000行を返すのにかかる20〜25秒のうちどれだけがネットワーク要因(ネットワークを介してデータを取得する)に関係し、どれくらいがクライアント要因(その約15 MBと残りの非SSMSのグリッドへのXMLデータ、またはディスクへの保存)
操作のこれら2つの側面を考慮に入れることは、単にデータを返さないことによって実行できる場合があります。さて、一時テーブルまたはテーブル変数を選択することを考えるかもしれませんが、これはいくつかの新しい変数を導入するだけです(つまり、ディスクI / O tempdb
、トランザクションログ書き込み、tempdbデータやログファイルの自動成長、バッファプールなどのスペース。これらの新しい要素はすべて、クエリ時間を実際に増加させる可能性があります。代わりに、通常、SQL_VARIANT
新しい行(つまりSELECT @Column1 = tab.Column1,...
)で上書きされる変数(適切なデータ型ではなく)に列を保存します。
もつとも、としてこのDBA.StackExchange Q&Aで@PaulWhiteによって指摘された、同じLOBデータにアクセスする際の論理は別の読み込みペーストビンに掲示自分の追加の研究で、(T-SQLスクリプトLOBの読み取りのためのさまざまなシナリオをテストするために) 、LOBは間で一貫してアクセスされないSELECT
、SELECT INTO
、SELECT @XmlVariable = XmlColumn
、SELECT @XmlVariable = XmlColumn.query(N'/')
、およびSELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
。そのため、ここでのオプションはもう少し制限されていますが、できることは次のとおりです。
- SSMSまたはSQLCMD.EXEでSQL Serverを実行しているサーバーでクエリを実行して、ネットワークの問題を排除します。
- [クエリオプション]-> [結果]-> [グリッド]に移動し、[実行後に結果を破棄する]オプションをオンにして、SSMSでクライアントの問題を除外します。このオプションは、メッセージを含むすべての出力を防止しますが、SSMSが各行にメモリを割り当ててグリッドに描画するのにかかる時間を除外するのに役立ちます。
または、SQLCMD.EXEを使用してクエリを実行し、次の方法で出力をどこにも行かないように指示することもできます-o NUL:
。
- このクエリに関連付けられた待機タイプはありますか?はいの場合、その待機タイプは何ですか?
何をしている実際のためのデータサイズXML
の列が返されますか?「TOP 1000」行に合計XML
データの不均衡に大きな部分が含まれている場合、テーブル全体のその列の平均サイズは実際には重要ではありません。TOP 1000行について知りたい場合は、それらの行を見てください。以下を実行してください:
SELECT TOP 1000 tab.*,
SUM(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [TotalXmlKBytes],
AVG(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [AverageXmlKBytes]
STDEV(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [StandardDeviationForXmlKBytes]
FROM SchemaName.TableName tab;
- 正確なテーブルスキーマ。すべてのインデックスを含む完全な
CREATE TABLE
ステートメントを提供してください。
- クエリプラン?それは投稿できるものですか?その情報はおそらく何も変更しませんが、それが間違っていないと間違っていると推測するよりも、それが間違っていないことを知っている方が良いです;-)
- データファイルに物理的/外部的な断片化はありますか?ここではこれは大きな要因ではないかもしれませんが、SSDや超高価なSATAではなく「消費者グレードのSATA」を使用しているため、特にこれらのセクターの数が多いほど、最適に注文されていないセクターの影響が顕著になります読む必要があるものが増えています。
次のクエリの正確な結果は何ですか:
SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(),
OBJECT_ID(N'dbo.SchemaName.TableName'), 1, 0, N'LIMITED');
更新
私は、このシナリオを再現して、同様の動作が発生するかどうかを確認する必要があることに気付きました。そこで、いくつかの列(質問のあいまいな説明に似ています)を含むテーブルを作成し、100万行を入力しました。XML列には行ごとに約15kのデータがあります(以下のコードを参照)。
私が見つけたのはSELECT TOP 1000 * FROM TABLE
、最初は8秒で完了し、その後は2〜4秒で完了したことです(はい、クエリのDBCC DROPCLEANBUFFERS
各実行前に実行されSELECT *
ます)。そして、私の数年前のラップトップは高速ではありません。SQLServer 2012 SP2 Developer Edition、64ビット、6 GB RAM、デュアル2.5 Ghz Core i5、および5400 RPM SATAドライブ。また、SSMS 2014、SQL Server Express 2014、Chromeなどを実行しています。
システムの応答時間に基づいて、20〜25秒の応答時間の原因を絞り込むために、より多くの情報(テーブルとデータの詳細、推奨されるテストの結果など)が必要であることを繰り返します。あなたが見ていること。
SET ANSI_NULLS, NOCOUNT ON;
GO
IF (OBJECT_ID(N'dbo.XmlReadTest') IS NOT NULL)
BEGIN
PRINT N'Dropping table...';
DROP TABLE dbo.XmlReadTest;
END;
PRINT N'Creating table...';
CREATE TABLE dbo.XmlReadTest
(
ID INT NOT NULL IDENTITY(1, 1),
Col2 BIGINT,
Col3 UNIQUEIDENTIFIER,
Col4 DATETIME,
Col5 XML,
CONSTRAINT [PK_XmlReadTest] PRIMARY KEY CLUSTERED ([ID])
);
GO
DECLARE @MaxSets INT = 1000,
@CurrentSet INT = 1;
WHILE (@CurrentSet <= @MaxSets)
BEGIN
RAISERROR(N'Populating data (1000 sets of 1000 rows); Set # %d ...',
10, 1, @CurrentSet) WITH NOWAIT;
INSERT INTO dbo.XmlReadTest (Col2, Col3, Col4, Col5)
SELECT TOP 1000
CONVERT(BIGINT, CRYPT_GEN_RANDOM(8)),
NEWID(),
GETDATE(),
N'<test>'
+ REPLICATE(CONVERT(NVARCHAR(MAX), CRYPT_GEN_RANDOM(1), 2), 3750)
+ N'</test>'
FROM [master].[sys].all_columns sac1;
IF ((@CurrentSet % 100) = 0)
BEGIN
RAISERROR(N'Executing CHECKPOINT ...', 10, 1) WITH NOWAIT;
CHECKPOINT;
END;
SET @CurrentSet += 1;
END;
--
SELECT COUNT(*) FROM dbo.XmlReadTest; -- Verify that we have 1 million rows
-- O.P. states that the "clustered index fragmentation is close to 0%"
ALTER INDEX [PK_XmlReadTest] ON dbo.XmlReadTest REBUILD WITH (FILLFACTOR = 90);
CHECKPOINT;
--
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS IO, TIME ON;
SELECT TOP 1000 * FROM dbo.XmlReadTest;
SET STATISTICS IO, TIME OFF;
/*
Scan count 1, logical reads 21, physical reads 1, read-ahead reads 4436,
lob logical reads 5676, lob physical reads 1, lob read-ahead reads 3967.
SQL Server Execution Times:
CPU time = 171 ms, elapsed time = 8329 ms.
*/
また、非LOBページの読み取りにかかる時間を除外するため、次のクエリを実行してXML列(上記で提案したテストの1つ)を除くすべてを選択しました。これは、ほぼ一貫して1.5秒で戻ります。
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS IO, TIME ON;
SELECT TOP 1000 ID, Col2, Col3, Col4 FROM dbo.XmlReadTest;
SET STATISTICS IO, TIME OFF;
/*
Scan count 1, logical reads 21, physical reads 1, read-ahead reads 4436,
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 1666 ms.
*/
結論(今のところ)
シナリオを再作成しようとする試みに基づいて、20〜25秒の主な原因としてSATAドライブまたは非シーケンシャルI / Oのいずれかを指すことはできないと思います。 XML列が含まれていない場合、クエリがどれだけ速く戻るかわかりません。そして、私は論理的には、あなたが表示されていること(非LOB)読み込みの多数を再現することができませんでしたが、私はそれに照らして各列にデータを追加する必要があることを感じていると声明の:
テーブルページの〜90%はLOB_DATAです
私のテーブルには100万の行があり、各行sys.dm_db_index_physical_stats
には15,000を超えるXMLデータがあり、200万のLOB_DATAページがあることを示しています。残りの10%は222kのIN_ROWデータページになりますが、そのうちの11,630しかありません。したがって、実際のテーブルスキーマと実際のデータに関する詳細情報が必要になります。