SQL Serverでは、選択した行グループがロックされているかどうかを確認する方法はありますか?


21

数十億行のテーブルの多数のレコードを更新/削除しようとしています。これは一般的なテーブルであるため、このテーブルのさまざまなセクションで多くのアクティビティがあります。すべての行のロックまたはページロックまたはテーブルロックの取得を待機しているため、大規模な更新/削除アクティビティが長時間ブロックされているため、タイムアウトになるか、タスクの完了に数日かかります。

そのため、一度に行の小さなバッチを削除するアプローチを変更しています。しかし、選択された(たとえば、100または1000または2000行)が現在別のプロセスによってロックされているかどうかを確認します。

  • そうでない場合は、削除/更新に進みます。
  • ロックされている場合は、次のグループのレコードに進みます。
  • 最後に、最初に戻って、残ったものを更新/削除してみてください。

これは実行可能ですか?

ありがとう、ToC


2
deleteステートメントまたはNOWAIT(グループ全体を失敗させるため)の一部としてREADPASTを調べましたか?これらのいずれかがあなたのために働くかもしれません。msdn.microsoft.com/en-us/library/ms187373.aspx
ショーンはサラチップスを削除します

@SeanGallardy私はその考えを考えていませんでしたが、今は考えます。しかし、特定の行がロックされているかどうかを確認する簡単な方法はありますか?ありがとう。
ToC

3
LOCK_TIMEOUT(msdn.microsoft.com/en-us/library/ms189470.aspx)を調べることもできます。たとえば、これは、Adam Machanicのsp_whoisactiveが、実行計画を収集するときにブロックされた場合にプロシージャが長く待たないようにする方法です。短いタイムアウトを設定するか、0の値を使用することもできます(「0は、ロックが発生するとすぐに待機せず、メッセージを返すことを意味します。」)これをTRY / CATCHと組み合わせて、エラー1222( 「ロックリクエストのタイムアウト期間を超えました」)、次のバッチに進みます。
ジェフパターソン

@gpatterson興味深いアプローチ。私もこれを試してみます。
ToC

2
答えは、アプリケーションで特別な処理が行われない限り、行がロックされているかどうかを確認する簡単な方法はありません。基本的には、最初にLOCKLOCKとXLOCKでlock_timeoutを設定して選択を行うことができます(これは、元のコメントのNOWAITの目的であり、タイムアウトを0に設定します)。取得できない場合は、何かがロックされていることがわかります。「何かによってロックされたインデックスZを使用して、テーブルYの行Xは何か」と言うのに簡単に利用できるものはありません。テーブルにロックがあるか、ページ/行/キー/などにロックがあるかを確認できますが、クエリの特定の行に変換するのは簡単ではありません。
ショーンは、サラチップスを削除する

回答:


10

要求を正しく理解している場合、目標は行のバッチを削除することであると同時に、テーブル全体の行でDML操作が発生しています。目標はバッチを削除することです。ただし、そのバッチで定義された範囲内に含まれる基本行がロックされている場合、そのバッチをスキップして次のバッチに移動する必要があります。その後、以前に削除されていないバッチに戻って、元の削除ロジックを再試行する必要があります。行の必要なバッチがすべて削除されるまで、このサイクルを繰り返す必要があります。

前述したように、ブロックされた行を含む可能性のある過去の範囲をスキップするには、READPASTヒントとREAD COMMITTED(デフォルト)分離レベルを使用するのが妥当です。さらに一歩進んで、SERIALIZABLE分離レベルの使用と削除のニブル化をお勧めします。

SQL Serverは、キー範囲ロックを使用して、シリアル化可能なトランザクション分離レベルを使用しながら、Transact-SQLステートメントによって読み取られるレコードセットに暗黙的に含まれる行の範囲を保護します...詳細はこちら:https : //technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx

削除をニブルすることで、私たちの目的は、行の範囲を分離し、削除中にそれらの行に変更が発生しないようにすることです。つまり、幻の読み取りや挿入を望まないことです。シリアライズ可能な分離レベルは、この問題を解決するためのものです。

私のソリューションを実証する前に、データベースのデフォルトの分離レベルをSERIALIZABLEに切り替えることを推奨しておらず、私のソリューションが最適であることも推奨していないことを付け加えます。私は単にそれを提示し、ここからどこに行くことができるかを見たいだけです。

いくつかのハウスキーピングノート:

  1. 私が使用しているSQL Serverバージョンは、Microsoft SQL Server 2012-11.0.5343.0(X64)です。
  2. 私のテストデータベースは完全復旧モデルを使用しています

実験を始めるために、テストデータベース、サンプルテーブルをセットアップし、テーブルに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分離レベルの実際の例をいくつか作成しました。それらは、以下のリンクから入手できるはずです。

削除操作

挿入操作

平等操作-次のキー値でのキー範囲ロック

等価操作-存在するデータのシングルトンフェッチ

等価操作-存在しないデータのシングルトンフェッチ

不等式操作-範囲および次のキー値のキー範囲ロック


9

そのため、一度に行の小さなバッチを削除するアプローチを変更しています。

これは、小さな慎重なバッチまたはチャンクで削除するのは本当に良い考えです。私は考えを追加小さなwaitfor delay '00:00:05'場合-とデータベースの復旧モデルに応じてFULL、その後、やるlog backupとあればSIMPLE、次に行うmanual CHECKPOINTトランザクションログの膨満感を避けるために-バッチ間。

しかし、選択された(たとえば、100または1000または2000行)が現在別のプロセスによってロックされているかどうかを確認します。

あなたが言っていることは、箱から出して完全に可能というわけではありません(あなたの3つの箇条書きを覚えておいてください)。上記の提案small batches + waitfor delayが機能しない場合(適切なテストを行っている場合)、を使用できますquery HINT

使用しないでくださいNOLOCK-を参照しキロバイト/ 308886Itzikベン・ガンによってSQL Serverの読み取り一貫性の問題をどこでもNOLOCKを置く-ことにより、アーロン・ベルトランSQL ServerのNOLOCKヒント&他の貧しい人々のアイデア

READPASTヒントはシナリオに役立ちます。READPASTヒントの要点は次のとおりです。行レベルのロックがある場合、SQLサーバーはそれを読み取りません。

データベースエンジンが、他のトランザクションによってロックされている行を読み取らないことを指定します。READPASTが指定されている場合、行レベルのロックはスキップされます。つまり、データベースエンジンは、ロックが解除されるまで現在のトランザクションをブロックするのではなく、行をスキップします。

限られたテスト中に、DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK)クエリセッション分離レベルをREAD COMMITTED使用SET TRANSACTION ISOLATION LEVEL READ COMMITTEDし、とにかく既定の分離レベルを使用するように設定すると、本当に良いスループットが見つかりました。


2

質問へのコメントで元々提供されていた他のアプローチの要約。


  1. NOWAIT互換性のないロックが発生するとすぐに望ましい動作がチャンク全体を失敗させる場合に使用します。

    NOWAITドキュメントから:

    テーブルでロックが発生するとすぐにメッセージを返すようにデータベースエンジンに指示します。特定のテーブルNOWAITを指定するのと同等SET LOCK_TIMEOUT 0です。NOWAITときのヒントは動作しませんTABLOCKヒントも含まれています。TABLOCKヒントの使用時に待機せずにクエリを終了するには、SETLOCK_TIMEOUT 0;代わりにクエリの前に付けます。

  2. SET LOCK_TIMEOUT同様の結果を達成するために使用しますが、構成可能なタイムアウトを使用します。

    ドキュメントからSET LOCK_TIMEOUT

    ロックが解除されるまでステートメントが待機するミリ秒数を指定します。

    ロックの待機がタイムアウト値を超えると、エラーが返されます。値0は、ロックが検出されるとすぐに待機せず、メッセージを返すことを意味します。


0

2つの並列クエリがあると仮定します。

接続/セッション1:行をロックします= 777

SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777

接続/セッション2:ロックされた行= 777を無視します

SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777

または接続/セッション2:例外をスローします

DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
  THROW 51000, 'Hi, a record is locked or does not exist.', 1;

-1

このようなものでフィルタリングしてみてください- 本当に、本当に具体的になりたい場合、複雑になる可能性があります。sys.dm_tran_locksの説明については、BOLをご覧ください

SELECT 
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE 
    WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
    ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id

ちょうど好奇心-なぜ下票ですか?
rottengeek

-11

削除中にNoLOCKを使用できます。行がロックされている場合、行は削除されません。それは理想的ではありませんが、あなたのためにトリックをするかもしれません。

DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True

7
私は私が得る私のローカルマシン上でそれをしようとするとMsg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements.2005年以降は非推奨
チームモニカ-トムV
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.