要求を正しく理解している場合、目標は行のバッチを削除することであると同時に、テーブル全体の行でDML操作が発生しています。目標はバッチを削除することです。ただし、そのバッチで定義された範囲内に含まれる基本行がロックされている場合、そのバッチをスキップして次のバッチに移動する必要があります。その後、以前に削除されていないバッチに戻って、元の削除ロジックを再試行する必要があります。行の必要なバッチがすべて削除されるまで、このサイクルを繰り返す必要があります。
前述したように、ブロックされた行を含む可能性のある過去の範囲をスキップするには、READPASTヒントとREAD COMMITTED(デフォルト)分離レベルを使用するのが妥当です。さらに一歩進んで、SERIALIZABLE分離レベルの使用と削除のニブル化をお勧めします。
SQL Serverは、キー範囲ロックを使用して、シリアル化可能なトランザクション分離レベルを使用しながら、Transact-SQLステートメントによって読み取られるレコードセットに暗黙的に含まれる行の範囲を保護します...詳細はこちら:https :
//technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
削除をニブルすることで、私たちの目的は、行の範囲を分離し、削除中にそれらの行に変更が発生しないようにすることです。つまり、幻の読み取りや挿入を望まないことです。シリアライズ可能な分離レベルは、この問題を解決するためのものです。
私のソリューションを実証する前に、データベースのデフォルトの分離レベルをSERIALIZABLEに切り替えることを推奨しておらず、私のソリューションが最適であることも推奨していないことを付け加えます。私は単にそれを提示し、ここからどこに行くことができるかを見たいだけです。
いくつかのハウスキーピングノート:
- 私が使用しているSQL Serverバージョンは、Microsoft SQL Server 2012-11.0.5343.0(X64)です。
- 私のテストデータベースは完全復旧モデルを使用しています
実験を始めるために、テストデータベース、サンプルテーブルをセットアップし、テーブルに2,000,000行を入力します。
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
ALTER DATABASE [test] SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
c1 BIGINT IDENTITY (1,1) NOT NULL
, c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows
INSERT INTO dbo.tbl
SELECT TOP 2000
number
FROM
master..spt_values
ORDER BY
number
GO 1000
この時点で、SERIALIZABLE分離レベルのロックメカニズムが機能できる1つ以上のインデックスが必要になります。
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2
ON dbo.tbl (c2);
GO
次に、2,000,000行が作成されたことを確認します。
SELECT
COUNT(*)
FROM
tbl;
したがって、データベース、テーブル、インデックス、および行があります。そこで、削除をニブルするための実験を設定しましょう。まず、典型的なニブル削除メカニズムを作成する最適な方法を決定する必要があります。
DECLARE
@BatchSize INT = 100
, @LowestValue BIGINT = 20000
, @HighestValue BIGINT = 20010
, @DeletedRowsCount BIGINT = 0
, @RowCount BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE @DeletedRowsCount < ( @HighestValue - @LowestValue )
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
DELETE
FROM
dbo.tbl
WHERE
c1 IN (
SELECT TOP (@BatchSize)
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN @LowestValue AND @HighestValue
ORDER BY
c1
);
SET @RowCount = ROWCOUNT_BIG();
COMMIT TRANSACTION;
SET @DeletedRowsCount += @RowCount;
WAITFOR DELAY '000:00:00.025';
CHECKPOINT;
END;
ご覧のとおり、明示的なトランザクションをwhileループ内に配置しました。ログのフラッシュを制限したい場合は、ループの外側に自由に配置してください。さらに、完全復旧モデルであるため、トランザクションログが過度に大きくなるのを防ぐために、ニブル削除操作の実行中にトランザクションログバックアップをより頻繁に作成することをお勧めします。
そのため、このセットアップにはいくつかの目標があります。まず、キー範囲ロックが必要です。そのため、バッチをできるだけ小さくするようにしています。また、「巨大な」テーブルの同時実行に悪影響を与えたくありません。だから、私はロックを取り、できるだけ早くそれらを残したいです。そのため、バッチサイズを小さくすることをお勧めします。
ここで、この削除ルーチンの実際の非常に短い例を提供したいと思います。SSMS内で新しいウィンドウを開き、テーブルから1行削除する必要があります。デフォルトのREAD COMMITTED分離レベルを使用して、暗黙的なトランザクション内でこれを行います。
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
この行は実際に削除されましたか?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
はい、削除されました。
次に、ロックを確認するために、SSMS内で新しいウィンドウを開いて、コードスニペットを1つまたは2つ追加します。:私はここで見つけることができアダムメカニックのsp_whoisactive、使用していますsp_whoisactiveを
SELECT
DB_NAME(resource_database_id) AS DatabaseName
, resource_type
, request_mode
FROM
sys.dm_tran_locks
WHERE
DB_NAME(resource_database_id) = 'test'
AND resource_type = 'KEY'
ORDER BY
request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
これで開始する準備ができました。新しいSSMSウィンドウで、削除した1行を再挿入しようとする明示的なトランザクションを開始しましょう。同時に、ニブル削除操作を実行します。
挿入コード:
BEGIN TRANSACTION
SET IDENTITY_INSERT dbo.tbl ON;
INSERT INTO dbo.tbl
( c1 , c2 )
VALUES
( 20005 , 1 );
SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
挿入で始まり、削除が続く両方の操作を開始しましょう。キー範囲ロックと排他ロックを確認できます。
挿入によってこれらのロックが生成されました。
ニブル削除/選択はこれらのロックを保持しています:
挿入が期待どおりに削除をブロックしています:
では、挿入トランザクションをコミットして、何が起きているのかを見てみましょう。
そして予想どおり、すべてのトランザクションが完了します。次に、挿入がファントムであったかどうか、または削除操作で削除されたかどうかを確認する必要があります。
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
実際、挿入は削除されました。そのため、ファントム挿入は許可されませんでした。
したがって、結論として、この演習の真の意図は、すべての単一の行、ページ、またはテーブルレベルのロックを試みて追跡し、バッチの要素がロックされているかどうかを判断しようとすることではないため、削除操作が必要になると思います待つ。それは質問者の意図であったかもしれません。ただし、そのタスクは非常に難しく、不可能ではないにしても基本的に非実用的です。本当の目標は、バッチの範囲を独自のロックで分離し、その後バッチを削除する前に、不要な現象が発生しないようにすることです。SERIALIZABLE分離レベルは、この目的を達成します。重要なのは、ニブルを小さく保ち、トランザクションログを制御し、不要な現象を排除することです。
速度が必要な場合は、パーティション化できないほど深くテーブルを構築しないでください。したがって、パーティションスイッチングを使用して最速の結果を得ることができません。速度の鍵は、パーティション化と並列処理です。苦しみの鍵はニブルとライブロックです。
ご意見をお聞かせください。
SERIALIZABLE分離レベルの実際の例をいくつか作成しました。それらは、以下のリンクから入手できるはずです。
削除操作
挿入操作
平等操作-次のキー値でのキー範囲ロック
等価操作-存在するデータのシングルトンフェッチ
等価操作-存在しないデータのシングルトンフェッチ
不等式操作-範囲および次のキー値のキー範囲ロック