MERGEデッドロック防止


9

私たちのデータベースの1つに、複数のスレッドによって集中的に同時にアクセスされるテーブルがあります。スレッドはを介して行を更新または挿入しますMERGE。行を時々削除するスレッドもあるので、テーブルデータは非常に不安定です。アップサートを実行するスレッドは時々デッドロックに悩まされます。この問題は、この質問で説明されている問題に似ています。ただし、違いは、私たちの場合、各スレッドが1行だけを更新または挿入することです。

簡略化されたセットアップは次のとおりです。テーブルは、2つの一意の非クラスター化インデックスを持つヒープです。

CREATE TABLE [Cache]
(
    [UID] uniqueidentifier NOT NULL CONSTRAINT DF_Cache_UID DEFAULT (newid()),
    [ItemKey] varchar(200) NOT NULL,
    [FileName] nvarchar(255) NOT NULL,
    [Expires] datetime2(2) NOT NULL,
    CONSTRAINT [PK_Cache] PRIMARY KEY NONCLUSTERED ([UID])
)
GO
CREATE UNIQUE INDEX IX_Cache ON [Cache] ([ItemKey]);
GO

典型的なクエリは

DECLARE
    @itemKey varchar(200) = 'Item_0F3C43A6A6A14255B2EA977EA730EDF2',
    @fileName nvarchar(255) = 'File_0F3C43A6A6A14255B2EA977EA730EDF2.dat';

MERGE INTO [Cache] WITH (HOLDLOCK) T
USING (
    VALUES (@itemKey, @fileName, dateadd(minute, 10, sysdatetime()))
) S(ItemKey, FileName, Expires)
ON T.ItemKey = S.ItemKey
WHEN MATCHED THEN
    UPDATE
    SET
        T.FileName = S.FileName,
        T.Expires = S.Expires
WHEN NOT MATCHED THEN
    INSERT (ItemKey, FileName, Expires)
    VALUES (S.ItemKey, S.FileName, S.Expires)
OUTPUT deleted.FileName;

つまり、一意のインデックスキーによって照合が行われます。ヒントHOLDLOCKはここにあります、それは同時性のためです(ここでアドバイスされます)。

簡単な調査を行ったところ、次のことがわかりました。

ほとんどの場合、クエリ実行プランは

インデックスシーク実行プラン

次のロックパターン

インデックスシークロックパターン

つまりIX、オブジェクトをロックしてから、より詳細なロックを実行します。

ただし、クエリ実行プランが異なる場合があります

テーブルスキャン実行計画

(この平面形状はINDEX(0)ヒントを追加することで強制できます)そしてそのロックパターンは

テーブルスキャンロックパターン

既に配置されたX後にオブジェクトに配置されたロックに注意しIXてください。

2つIXは互換性がありますが、2つは互換性Xがないため、同時実行で発生するのは

デッドロック

デッドロックグラフ

デッドロック

そして、ここで質問の最初の部分が発生します。適格X後にオブジェクトをロックしていますIXか?バグじゃないですか?

ドキュメントの状態:

意図的ロックは、より低いレベルでのロックの前に取得されるため、意図的ロックと呼ばれ、より低いレベルでロックを配置する意図を示します

そしてまた

IXは、すべてではなく一部の行のみを更新する意図を意味します

だから、私に非常に疑わしい見えたX後にオブジェクトをロックするIX

最初に、テーブルロックのヒントを追加してデッドロックを防止しようとしました

MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCK) T

そして

MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCKX) T

そのTABLOCK場でのロックパターンは

マージホールドロックタブロックロックパターン

そしてTABLOCKXロックパターンで

マージホールドロックtablockxロックパターン

2つSIX(および2つX)は互換性がないため、デッドロックは効果的に防止されますが、残念ながら同時実行も防止されます(これは望ましくありません)。

私の次の試みは、ロックを追加PAGLOCKROWLOCKて、よりきめ細かくして競合を減らすことでした。どちらも効果はありません(Xオブジェクトへの影響は直後にも確認されましたIX)。

私の最後の試みは、FORCESEEKヒントを追加することにより、「適切な」実行プランの形状を、きめ細かいロックで強制することでした

MERGE INTO [Cache] WITH (HOLDLOCK, FORCESEEK(IX_Cache(ItemKey))) T

そしてそれは働いた。

そして、ここで質問の2番目の部分が発生します。それはそれが起こる可能性がFORCESEEK無視され、不正なロックパターンが使用されるのですか?(私が述べたように、PAGLOCKそしてROWLOCK一見無視されました)。


追加しUPDLOCKても効果はありません(のX後もまだ観測可能なオブジェクトに対してIX)。

作成IX_Cacheインデックスがクラスタ化され、予想されるとして、働いていました。これにより、クラスター化インデックスシークと詳細なロックを使用した計画が可能になりました。さらに、詳細なロックも示すクラスター化インデックススキャンを強制的に実行しました。

しかしながら。追加の観察。元のセットアップでFORCESEEK(IX_Cache(ItemKey)))、配置されていても、@itemKey変数宣言をvarchar(200)からnvarchar(200)に変更すると、実行プランは次のようになります。

nvarcharを使用したインデックスシーク実行プラン

シークが使用されていることを確認してください。ただし、この場合のロックパターンはX、オブジェクトのロックがの後であることを示していますIX

したがって、強制シークでは必ずしも細かいロック(およびデッドロックの不在)が保証されるわけではないようです。クラスタ化されたインデックスを使用すると、きめ細かなロックが保証されるとは確信していません。それともそうですか?

私が理解していること(間違っている場合は訂正してください)は、ロックは状況によって大きく異なり、特定の実行プランの形状は特定のロックパターンを意味するものではないということです。

まだ開いたX後にオブジェクトをロックする適格性に関する質問IX。そして、それが適格である場合、オブジェクトのロックを防ぐためにできることはありますか?


feedback.azure.comでの関連リクエスト
i-one

回答:


9

オブジェクトを配置したIX後、配置することはXできますか?バグかどうか?

少し奇妙に見えますが、有効です。IXが取得された時点ではX、より低いレベルでロックを取得することが意図されている場合があります。そのようなロックが実際に行われなければならないということは言うまでもありません。結局のところ、下位レベルでロックするものは何もないかもしれません。エンジンは事前にそれを知ることができません。加えて、低レベルのロックが(例えばため省略することができるような最適化があるかもしれないISSロックが見ることができ、ここ)。

より具体的に言うと、現在のシナリオでは、シリアライズ可能なキー範囲ロックはヒープで使用できないためX、オブジェクトレベルでのロックのみが選択肢となります。その意味で、エンジンはX、アクセス方法がヒープスキャンの場合、必然的にロックが必要になることを早期に検出できるため、IXロックの取得を回避できます。

一方、ロックは複雑であり、意図的なロックは、必ずしもより低いレベルのロックを取得する意図とは関係のない内部的な理由で取得される場合があります。テイクIXは、一部のあいまいなエッジケースに必要な保護を提供する最も侵襲性の低い方法である可能性があります。同様の考慮事項については、IsolationLevel.ReadUncommittedで発行される共有ロックを参照してください。

したがって、現在の状況はデッドロックシナリオにとって不幸であり、原則として回避できる可能性がありますが、それは必ずしも「バグ」と同じではありません。この問題について明確な回答が必要な場合は、通常のサポートチャネルまたはMicrosoft Connectで問題を報告できます。

それはそれが起こる可能性がFORCESEEK無視され、不正なロックパターンが使用されるのですか?

No. FORCESEEKはヒントではなく、指令です。オプティマイザが「ヒント」を尊重するプランを見つけられない場合、エラーが発生します。

インデックスの強制は、キー範囲ロックを確実に取得できるようにする方法です。行を変更するためのアクセス方法を処理するときに自然に取られる更新ロックと合わせて、これはシナリオで同時実行の問題を回避するための十分な保証を提供します。

テーブルのスキーマが変更されない場合(新しいインデックスの追加など)、ヒントは、このクエリ自体によるデッドロックを回避するのにも十分です。非クラスター化インデックスの前にヒープにアクセスする可能性のある他のクエリ(非クラスター化インデックスのキーの更新など)で循環デッドロックが発生する可能性があります。

...からの変数宣言varchar(200)nvarchar(200)...

これは、単一の行が影響を受けるという保証を破るので、ハロウィーン保護のためにEager Table Spoolが導入されています。これのさらなる回避策として、保証を明示的にしてくださいMERGE TOP (1) INTO [Cache]...

私の理解[...]は、ロックは状況によって大きく異なり、特定の実行プランの形状は特定のロックパターンを意味するものではないということです。

実行計画に表示されていることは確かに多くあります。プランガイドなどを使用して特定のプラン形状を強制できますが、エンジンは実行時に異なるロックを取得することを決定する場合があります。TOP (1)上記の要素を組み込むと、可能性はかなり低くなります。

総論

ヒープテーブルがこのように使用されているのを見るのは少し珍しいことです。おそらくコメントでDan Guzmanが提案したインデックスを使用して、それをクラスター化されたテーブルに変換するメリットを考慮する必要があります。

CREATE UNIQUE CLUSTERED INDEX IX_Cache ON [Cache] ([ItemKey]);

これには、スペースの再利用に関する重要な利点があるだけでなく、現在のデッドロックの問題に対する適切な回避策が提供されます。

MERGEまた、同時実行性の高い環境で見られるのは少し変わっています。やや直観に反して、別々のINSERTand UPDATEステートメントを実行する方が効率的であることがよくあります。次に例を示します。

DECLARE
    @itemKey varchar(200) = 'Item_0F3C43A6A6A14255B2EA977EA730EDF2',
    @fileName nvarchar(255) = 'File_0F3C43A6A6A14255B2EA977EA730EDF2.dat';

BEGIN TRANSACTION;

    DECLARE @expires datetime2(2) = DATEADD(MINUTE, 10, SYSDATETIME());

    UPDATE TOP (1) dbo.Cache WITH (SERIALIZABLE, UPDLOCK)
    SET [FileName] = @fileName,
        Expires = @expires
    OUTPUT Deleted.[FileName]
    WHERE
        ItemKey = @itemKey;

    IF @@ROWCOUNT = 0
        INSERT dbo.Cache
            (ItemKey, [FileName], Expires)
        VALUES
            (@itemKey, @fileName, @expires);

COMMIT TRANSACTION;

RIDルックアップが不要になったことに注意してください。

実行計画

ItemKey(質問のように)の一意のインデックスの存在を保証できる場合は、の冗長性TOP (1)UPDATE削除して、より簡単な計画を与えることができます。

簡略化された更新

INSERTUPDATE計画の両方とも、どちらの場合でも簡単な計画の対象となります。MERGE常に完全なコストベースの最適化が必要です。

使用する正しいパターンとの詳細については、関連するQ&A SQL Server 2014同時入力の問題を参照してくださいMERGE

デッドロックは常に防止できるわけではありません。コーディングと設計を慎重に行うことで最小限に抑えることができますが、アプリケーションは常に奇妙なデッドロックを適切に処理できるように準備する必要があります(条件を再確認してから再試行するなど)。

問題のオブジェクトにアクセスするプロセスを完全に制御できる場合は、SQL Serverの同時挿入と削除で説明されているように、アプリケーションロックを使用して個々の要素へのアクセスをシリアル化することも検討してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.