この質問は、このフォーラムスレッドに関連しています。
ワークステーションで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_stats
DMV の結果(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
、ルックアップされたページのサンプリングの出力を比較しましたが、変更はありませんでした(ALLOCATED
PFSからビットのみが削除されました)。単に割り当てを解除します。
ページの検索/割り当て解除が問題の原因であることをさらに証明するために、vanilla varbinary(max)の代わりにfilestream列を使用して同じテストを試みました。削除は、LOBサイズに関係なく一定の時間でした。
だから、最初に私の学術的な質問:
- Xロックするために、SQL ServerがすべてのLOBデータページを検索する必要があるのはなぜですか?これは、メモリ内でロックがどのように表されているか(ページと一緒に保存されている)の詳細にすぎませんか?これにより、完全にキャッシュされない場合、I / Oの影響はデータサイズに大きく依存します。
- 割り当てを解除するためだけにXがロックされるのはなぜですか?割り当て解除ではページ自体を変更する必要がないため、行内部分でインデックスリーフのみをロックするだけでは十分ではありませんか?ロックが保護するLOBデータを取得する他の方法はありますか?
- この種の作業専用のバックグラウンドタスクが既にあるのに、なぜページの割り当てをまったく解除するのですか?
そしておそらくもっと重要なのは、私の実際的な質問です:
- 削除の動作を変える方法はありますか?私の目標は、ファイルストリームと同様に、サイズに関係なく一定時間で削除することです。ファイルストリームでは、事後のバックグラウンドでクリーンアップが行われます。構成の問題ですか?奇妙に物を保管していますか?
以下に、説明したテスト(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
削除の一部としてデータがトランザクションログに書き込まれているという理論をテストしましたが、そうではないようです。私はこれを間違ってテストしていますか?下記参照。
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の行を保存および削除するのが本当に唯一の人でない限り、これはより一般的な問題になると思いますか?他の誰もが何らかの形のガベージコレクションジョブでこれを回避しますか?