SQL ServerのLOBデータのパフォーマンスを削除する


16

この質問は、このフォーラムスレッドに関連しています

ワークステーションでSQL Server 2008 Developer Editionを実行し、「アルファクラスター」と呼ばれるEnterprise Editionの2ノード仮想マシンクラスターを実行しています。

varbinary(max)列のある行を削除するのにかかる時間は、その列のデータの長さに直接関係します。最初は直感的に聞こえるかもしれませんが、調査の後、SQL Serverが実際に一般的に行を削除し、この種のデータを処理する方法についての私の理解と衝突します。

この問題は、.NET Webアプリケーションで発生している削除タイムアウト(> 30秒)の問題に起因していますが、この説明のために単純化しています。

レコードが削除されると、SQL Serverは、トランザクションのコミット後、ゴーストクリーンアップタスクによってクリーンアップされるゴーストとしてマークします(Paul Randalのブログを参照)。varbinary(max)列にそれぞれ16 KB、4 MB、および50 MBのデータを含む3つの行を削除するテストでは、データの行内の部分を含むページとトランザクションでこれが発生しますログ。

奇妙なことに、削除中にすべてのLOBデータページにXロックが設定され、PFSでページの割り当てが解除されます。これは、トランザクションログ、sp_lockおよびdm_db_index_operational_statsDMV の結果(page_lock_count)で確認できます()。

これらのページがバッファキャッシュにまだない場合、ワークステーションとアルファクラスタでI / Oボトルネックが発生します。実際、page_io_latch_wait_in_ms同じDMVからの削除は実質的に削除の全期間であり、page_io_latch_wait_countロックされたページの数に対応します。ワークステーション上の50 MBファイルの場合、空のバッファーキャッシュ(checkpoint/ dbcc dropcleanbuffers)で開始すると3秒以上になりますが、断片化が重く、負荷がかかっている場合はより長くなることは間違いありません。

私はそれがキャッシュ内のスペースを割り当てているだけではないことを確認しようとしました。checkpointメソッドの代わりに削除を実行する前に、他の行から2 GBのデータを読み取りました。これは、SQL Serverプロセスに割り当てられている以上のものです。SQL Serverがデータをシャッフルする方法がわからないため、それが有効なテストであるかどうかはわかりません。私はそれが常に新しいものを支持して古いものを押し出すと思いました。

さらに、ページを変更することさえしません。これは私が見ることができますdm_os_buffer_descriptors。削除後、ページはきれいになりますが、変更されたページの数は、小規模、中規模、および大規模の3つの削除すべてで20未満です。またDBCC PAGE、ルックアップされたページのサンプリングの出力を比較しましたが、変更はありませんでした(ALLOCATEDPFSからビットのみが削除されました)。単に割り当てを解除します。

ページの検索/割り当て解除が問題の原因であることをさらに証明するために、vanilla varbinary(max)の代わりにfilestream列を使用して同じテストを試みました。削除は、LOBサイズに関係なく一定の時間でした。

だから、最初に私の学術的な質問:

  1. Xロックするために、SQL ServerがすべてのLOBデータページを検索する必要があるのはなぜですか?これは、メモリ内でロックがどのように表されているか(ページと一緒に保存されている)の詳細にすぎませんか?これにより、完全にキャッシュされない場合、I / Oの影響はデータサイズに大きく依存します。
  2. 割り当てを解除するためだけにXがロックされるのはなぜですか?割り当て解除ではページ自体を変更する必要がないため、行内部分でインデックスリーフのみをロックするだけでは十分ではありませんか?ロックが保護するLOBデータを取得する他の方法はありますか?
  3. この種の作業専用のバックグラウンドタスクが既にあるのに、なぜページの割り当てをまったく解除するのですか?

そしておそらくもっと重要なのは、私の実際的な質問です:

  • 削除の動作を変える方法はありますか?私の目標は、ファイルストリームと同様に、サイズに関係なく一定時間で削除することです。ファイルストリームでは、事後のバックグラウンドでクリーンアップが行われます。構成の問題ですか?奇妙に物を保管していますか?

以下に、説明したテスト(SSMSクエリウィンドウで実行)を再現する方法を示します。

CREATE TABLE [T] (
    [ID] [uniqueidentifier] NOT NULL PRIMARY KEY,
    [Data] [varbinary](max) NULL
)

DECLARE @SmallID uniqueidentifier
DECLARE @MediumID uniqueidentifier
DECLARE @LargeID uniqueidentifier

SELECT @SmallID = NEWID(), @MediumID = NEWID(), @LargeID = NEWID()
-- May want to keep these IDs somewhere so you can use them in the deletes without var declaration

INSERT INTO [T] VALUES (@SmallID, CAST(REPLICATE(CAST('a' AS varchar(max)), 16 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@MediumID, CAST(REPLICATE(CAST('a' AS varchar(max)), 4 * 1024 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@LargeID, CAST(REPLICATE(CAST('a' AS varchar(max)), 50 * 1024 * 1024) AS varbinary(max)))

-- Do this before test
CHECKPOINT
DBCC DROPCLEANBUFFERS
BEGIN TRAN

-- Do one of these deletes to measure results or profile
DELETE FROM [T] WHERE ID = @SmallID
DELETE FROM [T] WHERE ID = @MediumID
DELETE FROM [T] WHERE ID = @LargeID

-- Do this after test
ROLLBACK

ワークステーションで削除をプロファイリングした結果の一部を次に示します。

| 列タイプ| サイズを削除| 期間(ミリ秒)| 読む| 書き込み| CPU |
-------------------------------------------------- ------------------
| VarBinary | 16 KB | 40 | 13 | 2 | 0 |
| VarBinary | 4 MB | 952 | 2318 | 2 | 0 |
| VarBinary | 50 MB | 2976 | 28594 | 1 | 62 |
-------------------------------------------------- ------------------
| FileStream | 16 KB | 1 | 12 | 1 | 0 |
| FileStream | 4 MB | 0 | 9 | 0 | 0 |
| FileStream | 50 MB | 1 | 9 | 0 | 0 |

必ずしも代わりにファイルストリームを使用できるわけではありません:

  1. データサイズの分布は保証しません。
  2. 実際には、多くのチャンクでデータを追加しますが、ファイルストリームは部分的な更新をサポートしていません。これを考慮して設計する必要があります。

アップデート1

削除の一部としてデータがトランザクションログに書き込まれているという理論をテストしましたが、そうではないようです。私はこれを間違ってテストしていますか?下記参照。

SELECT MAX([Current LSN]) FROM fn_dblog(NULL, NULL)
--0000002f:000001d9:0001

BEGIN TRAN
DELETE FROM [T] WHERE ID = @ID

SELECT
    SUM(
        DATALENGTH([RowLog Contents 0]) +
        DATALENGTH([RowLog Contents 1]) +
        DATALENGTH([RowLog Contents 3]) +
        DATALENGTH([RowLog Contents 4])
    ) [RowLog Contents Total],
    SUM(
        DATALENGTH([Log Record])
    ) [Log Record Total]
FROM fn_dblog(NULL, NULL)
WHERE [Current LSN] > '0000002f:000001d9:0001'

サイズが5 MBを超えるファイルの場合、これはを返しました1651 | 171860

さらに、データがログに書き込まれると、ページ自体がダーティになると予想されます。割り当て解除のみがログに記録されているようで、削除後のダーティと一致します。

更新2

ポール・ランダルから返事をもらった。彼は、ツリーを横断して割り当て解除するページを見つけるためにすべてのページを読み取る必要があるという事実を確認し、どのページを検索する他の方法はないと述べました。これは、1と2の半分の答えです(ただし、行外データのロックの必要性は説明されていませんが、それは小さなポテトです)。

質問3はまだ開いています:削除のクリーンアップを行うバックグラウンドタスクが既にある場合、ページの割り当てを解除するのはなぜですか?

そしてもちろん、すべての重要な質問:このサイズに依存する削除動作を直接軽減する(つまり回避しない)方法はありますか?SQL Serverで50 MBの行を保存および削除するのが本当に唯一の人でない限り、これはより一般的な問題になると思いますか?他の誰もが何らかの形のガベージコレクションジョブでこれを回避しますか?


もっと良い解決策があればいいのにとは思いませんが、見つけられませんでした。最大1MB以上のさまざまなサイズの行を大量に記録する状況があり、古いレコードを削除する「パージ」プロセスがあります。削除は非常に遅いため、2つのステップに分割する必要がありました。最初にテーブル間の参照を削除し(非常に高速です)、孤立した行を削除します。削除ジョブは、データを削除するのに平均で約2.2秒/ MBでした。したがって、当然、競合を減らす必要があったため、行が削除されなくなるまで、ループ内に "DELETE TOP(250)"を持つストアドプロシージャがあります。
そろばん

回答:


5

ファイルストリームと比較してVARBINARY(MAX)を削除する方がはるかに非効率的である理由を正確に言うことはできませんが、これらのLOBを削除するときにWebアプリケーションのタイムアウトを避けようとしている場合に考えられる1つのアイデアです。VARBINARY(MAX)値を、元のテーブル(このtblParentと呼ぶ)によって参照される別のテーブル(tblLOBと呼ぶ)に格納できます。

ここからレコードを削除するときは、親レコードからレコードを削除してから、時折ガベージコレクションプロセスを実行して、LOBテーブル内のレコードをクリーンアップできます。このガベージコレクションプロセス中に追加のハードドライブアクティビティが発生する可能性がありますが、少なくともフロントエンドWebから分離され、ピーク時以外に実行できます。


ありがとう。これはまさにボード上のオプションの1つです。テーブルはファイルシステムであり、現在、バイナリデータを階層メタから完全に独立したデータベースに分離するプロセスにあります。あなたが言ったようにして階層行を削除し、GCプロセスに孤立したLOB行をクリーンアップさせることができます。または、同じ目標を達成するために、データと共に削除タイムスタンプを使用します。これは、問題に対する満足のいく答えがない場合に取ることができるルートです。
ジェレミーローゼンバーグ

1
削除されたことを示すタイムスタンプのみを使用する場合は注意が必要です。これは機能しますが、最終的にはアクティブな行で多くの使用スペースが占有されます。削除する量に応じて、ある時点で何らかの種類のgcプロセスが必要になります。また、時々削除するよりも定期的に削除するほうが影響は少なくなります。
イアンチェンバー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.