LOB_DATA、遅いテーブルスキャン、およびいくつかのI / Oに関する質問


19

列の1つがXMLデータで、XMLエントリの平均サイズが約15キロバイトのかなり大きなテーブルがあります。他のすべての列は、通常のint、bigint、GUIDなどです。具体的な数値を得るために、テーブルの行数が100万で、サイズが最大15 GBであるとします。

私が気づいたのは、すべての列を選択したい場合、このテーブルからのデータ選択が本当に遅いということです。私がする時

SELECT TOP 1000 * FROM TABLE

ディスクからデータを読み取るのに約20〜25秒かかります-結果に順序を付けませんが。コールドキャッシュを使用して(つまり、後にDBCC DROPCLEANBUFFERS)クエリを実行します。IO統計の結果は次のとおりです。

スキャンカウント1、論理読み取り364、物理読み取り24、先読み読み取り7191、lob論理読み取り7924、lob物理読み取り1690、lob先読み読み取り3968

最大15 MBのデータを取得します。実行計画には、予想どおりクラスター化インデックススキャンが表示されます。

クエリ以外にディスクでIOが実行されていません。また、クラスター化インデックスの断片化が0%に近いことも確認しました。これは一般消費者向けのSATAドライブですが、SQL Serverは〜100-150 MB / minよりも速くテーブルをスキャンできると思います。

XMLフィールドが存在すると、ほとんどのテーブルデータがLOB_DATAページに配置されます(実際、テーブルページの約90%がLOB_DATAです)。

私の質問は-LOB_DATAページはサイズだけでなく、テーブルに多くのLOB_DATAページがある場合にSQL Serverがクラスター化インデックスを効果的にスキャンできないため、低速スキャンを引き起こす可能性があると考えるのは正しいですか?

さらに広く-そのようなテーブル構造/データパターンを持つことは合理的であると考えられていますか?Filestreamを使用する際の推奨事項では、通常、フィールドサイズがはるかに大きくなるため、実際にはそのような道を行きたくありません。私はこの特定のシナリオに関する良い情報を実際に見つけていません。

私はXML圧縮を検討してきましたが、クライアント上またはSQLCLRで行う必要があり、システムに実装するにはかなりの作業が必要になります。

圧縮を試みましたが、XMLは非常に冗長であるため、(ac#アプリで)XMLを20KBから〜2.5KBに圧縮し、VARBINARY列に格納して、LOBデータページの使用を防ぎます。これにより、テストでSELECTが20倍高速化されます。


アレックス:私の答えに関連する議論を見たかどうかはわかりません(リンクは私の答えの下のコメントにあります)が、私はあなたのシナリオを再現することに近づくことができました。私はあなたの説明に一致するテーブルを作成し(情報がある限り)、非常に類似したI / O統計を取得しました。例外として、「LOB Physical Reads」は決して近いものではありませんでした。したがって、XMLを更新したのか(他の列は更新していないのか)、データファイルの物理的な断片化が多かったのではないかと思っていました。テーブルのDDLと各データファイルの自動拡張設定を取得してもかまいませんが、データファイルを圧縮しますか?
ソロモンラツキー16

まず第一に、詳細な回答に感謝します。時間がないため、その時点では議論に参加できませんでした。あなたはこれについて言及したので(質問されたときは思いもしませんでした)、XMLフィールドは作成後に何度も更新され、小さく作成されます。そのため、最初は行に格納され、いくつかの更新後にLOBページ構造に移動され、さらに更新が行われると思われます。
アレクサンダーシェルミン16

(続き)質問をする前にファイルの物理的な断片化をチェックし、組み込みのWindowsツールは問題ないと判断したので、これ以上は調べませんでした。自動拡張はデフォルトでは1 MBであり、データファイルは圧縮されていません。
アレクサンダー・シェルミン16

私の特定のケースでは、上位1000 *を選択することが重要です。私は確かにそれが悪い慣行であると考えられていることを理解していますが、いくつかのアプリケーション設計の決定は、長い間実施された後に変更するのが本当に難しいです。Select *は、基本的に、アプリ内の異なるコンポーネント間のデータベース間レプリケーション戦略として使用されます。これには長所があります。たとえば、オンザフライでデータ/スキーマを使用して多くの任意の操作を行うことができます。これは、組み込みの複製技術では困難ですが、問題が伴います。
アレクサンダー・シェルミン16

SELECT *XMLデータが必要な場合、Alex は問題ではありません。XMLデータが必要ない場合にのみ問題になります。その場合、使用しないデータを取得するためにクエリを遅くするのはなぜですか?LOBページの断片化が正確に報告されていないのではないかと考えて、XMLの更新について質問しました。クラスター化インデックスが断片化されていないことをどのように正確に判断したのですか?実行したコマンドを提供できますか?そして、クラスター化インデックスで完全な再構築を行いましたか?(続き)
ソロモンラッツキー

回答:


11

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割り当てがあります。

  1. インラインルート-スペースが許す8001〜40,000(実際には42,000)バイトのデータの場合、LOBページを直接指す1〜5個のポインター(24〜72バイト)IN ROWがあります。
  2. 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バイト、FILLFACTOR100のバイトがあると仮定すると、次のようになります。

  • 圧縮なし: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 *。これは、アプリケーションが発行するクエリである可能性は低いため、不必要な最適化の唯一の基盤として使用しないでください。

より詳細な情報と試すべきテストについては、以下のセクションを参照してください。


この質問に明確な答えを与えることはできませんが、少なくともある程度の進歩を遂げ、正確な問題の解明に近づけるために追加の研究を提案することができます(証拠に基づいて)。

私たちが知っていること:

  1. テーブルには約100万行あります
  2. テーブルサイズは約15 GBです
  3. 表には、いずれかが含まれXML、列と種類のいくつかの他の列を:INTBIGINTUNIQUEIDENTIFIER、「など」
  4. XML列「サイズ」は、平均で約15k
  5. 実行後DBCC DROPCLEANBUFFERS、次のクエリが完了するまでに20〜25秒かかります。SELECT TOP 1000 * FROM TABLE
  6. クラスター化インデックスがスキャンされています
  7. クラスタ化インデックスの断片化は0%に近い

私たちが知っていると思うこと:

  1. これらのクエリ以外のディスクアクティビティはありません。本気ですか?他のユーザークエリがない場合でも、バックグラウンド操作が行われていますか?IOの一部を使用している可能性のある同じマシンで実行されているSQL Serverの外部にプロセスがありますか?ないかもしれませんが、提供された情報だけに基づいて明確ではありません。
  2. 15 MBのXMLデータが返されています。この数値は何に基づいていますか?1000行×1行あたり15kのXMLデータの平均から導き出された推定?または、そのクエリで受け取ったもののプログラムによる集計ですか?それが単なる推定である場合、XMLデータの分布は単純な平均によって暗示される方法でさえ均一ではないかもしれないので、私はそれに頼りません。
  3. XML圧縮が役立つ場合があります。.NETで圧縮をどの程度正確に行いますか?GZipStreamまたはDeflateStreamクラス経由?これはゼロコストオプションではありません。確かにデータの一部を大幅に圧縮しますが、毎回データを圧縮/解凍するための追加プロセスが必要になるため、より多くのCPUも必要になります。この計画では、次の機能も完全に削除されます。

    • 経由して、クエリXMLデータ.nodes.value.query、および.modifyXML機能。
    • XMLデータにインデックスを付けます。

      (XMLは「非常に冗長」であると述べたので)覚えておいてください。したがって、GZip(またはその他)を介した圧縮から得られる利益は、要素および/または属性値を圧縮することによってのみ発見されます。上記の機能。XMLであると述べたため、要素と属性名をディクショナリに保存し、各アイテムに整数インデックスIDを割り当て、その整数IDを使用することでデータ型が既に最適化されていることにドキュメント全体で使用します(したがって、使用ごとにフルネームを繰り返したり、要素の終了タグとして繰り返したりすることはありません)。また、実際のデータから余分な空白が削除されます。これが、抽出されたXMLドキュメントが元の構造を保持せず、空の要素が次のように入力されたとして<element />も抽出される理由です。<element></element>

      また、XMLデータを圧縮してVARBINARY(MAX)結果を保存しても、LOBアクセスが排除されるわけではなく、単に削減されることに注意してください。行の残りのデータのサイズによっては、圧縮された値が行内に収まる場合や、LOBページが必要な場合があります。

その情報は、有用ではありますが、ほとんど十分ではありません。クエリのパフォーマンスに影響する要因は多数あるため、何が起こっているのかをより詳細に把握する必要があります。

わからないが必要なこと:

  1. なぜパフォーマンスがSELECT *重要なのですか?これは、コードで使用するパターンですか。もしそうなら、なぜですか?
  2. XML列のみを選択した場合のパフォーマンスはどうですか?次のことを行う場合の統計とタイミングはどうなりますSELECT TOP 1000 XmlColumn FROM TABLE;か?
  3. これらの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は間で一貫してアクセスされないSELECTSELECT INTOSELECT @XmlVariable = XmlColumnSELECT @XmlVariable = XmlColumn.query(N'/')、およびSELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)。そのため、ここでのオプションはもう少し制限されていますが、できることは次のとおりです。

    1. SSMSまたはSQLCMD.EXEでSQL Serverを実行しているサーバーでクエリを実行して、ネットワークの問題を排除します。
    2. [クエリオプション]-> [結果]-> [グリッド]に移動し、[実行後に結果を破棄する]オプションをオンにして、SSMSでクライアントの問題を除外します。このオプションは、メッセージを含むすべての出力を防止しますが、SSMSが各行にメモリを割り当ててグリッドに描画するのにかかる時間を除外するのに役立ちます。
      または、SQLCMD.EXEを使用してクエリを実行し、次の方法で出力をどこにも行かないように指示することもできます-o NUL:
  4. このクエリに関連付けられた待機タイプはありますか?はいの場合、その待機タイプは何ですか?
  5. 何をしている実際のためのデータサイズ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;
  6. 正確なテーブルスキーマ。すべてのインデックスを含む完全な CREATE TABLEステートメントを提供してください。
  7. クエリプラン?それは投稿できるものですか?その情報はおそらく何も変更しませんが、それが間違っていないと間違っていると推測するよりも、それが間違っていないことを知っている方が良いです;-)
  8. データファイルに物理的/外部的な断片化はありますか?ここではこれは大きな要因ではないかもしれませんが、SSDや超高価なSATAではなく「消費者グレードのSATA」を使用しているため、特にこれらのセクターの数が多いほど、最適に注文されていないセクターの影響が顕著になります読む必要があるものが増えています。
  9. 次のクエリの正確な結果は何ですか:

    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しかありません。したがって、実際のテーブルスキーマと実際のデータに関する詳細情報が必要になります。



10

LOB_DATAページはサイズだけでなく、SQL Serverがクラスター化インデックスを効果的にスキャンできないため、低速スキャンを引き起こす可能性があると思うのは正しいですか

はい、行に格納されていないLOBデータを読み取ると、シーケンシャルIOではなくランダムIOが発生します。ここで高速または低速の理由を理解するために使用するディスクパフォ​​ーマンスメトリックは、ランダム読み取りIOPSです。

LOBデータは、クラスター化インデックスのデータページが実際のLOBデータを指すLOBルート構造を持つLOBデータページを指すツリー構造に格納されます。クラスタ化インデックス内のルートノードを走査する場合、SQL Serverはシーケンシャルリードによってのみ行データを取得できます。LOBデータを取得するには、SQL Serverはディスク上の別の場所に移動する必要があります。

SSDのランダムIOPSは回転するディスクのIOPSよりもはるかに高いため、SSDディスクに変更してもそれほど苦しむことはないと思います。

そのようなテーブル構造/データパターンを持つことは合理的であると考えられていますか?

はい、可能性があります。このテーブルが何をしているかに依存します。

通常、SQL ServerでのXMLのパフォーマンスの問題は、T-SQLを使用してXMLにクエリを実行する場合、さらにXMLの値をwhere句または結合の述語で使用する場合に発生します。その場合は、プロパティの昇格選択的なXMLインデックス、またはXMLをテーブルに細断するテーブル構造の再設計を見ることができます。

圧縮してみました

私は10年以上前に製品で一度それをしましたが、それ以来ずっと後悔しています。T-SQLを使用してデータを操作できないことを本当に懐かしく思ったので、回避できる場合は誰にもお勧めしません。


答えてくれてありがとう。圧縮に関して:T-SQLからそのデータを実際にクエリする必要性は、保存されているデータの性質に明らかに依存するため、このような厳密なアンチ推奨が正当化されるかどうかはわかりません。私の場合、今のところ圧縮を使用することにしました。
アレクサンダーシェルミン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.