私はMERGE
クエリでデッドロックを回避しようとしています。クエリは異なるスレッドによって呼び出され、同じパラメーターで実行時に重複する可能性があります。このクエリでの私の経験は この質問 で説明されているシナリオと非常に似ており、参照用に以下のクエリをリストしました。
CREATE PROCEDURE MergeIt
@dataToMerge MyTableType READONLY
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET XACT_ABORT ON;
BEGIN TRANSACTION
MERGE INTO TargetTable WITH(HOLDLOCK) AS [target]
USING @dataToMerge AS [source]
ON [source].KeyPart_1 = [target].KeyPart_1 AND
[source].KeyPart_2 = [target].KeyPart_2
WHEN NOT MATCHED THEN
INSERT(Data, KeyPart_1, KeyPart_2)
VALUES([source].Data, [source].KeyPart_1, [source].KeyPart_2)
WHEN MATCHED THEN
UPDATE SET [target].Data = [source].Data,
[target].KeyPart_1 = [source].KeyPart_1,
[target].KeyPart_2 = [source].KeyPart_2;
COMMIT TRANSACTION
RETURN 0
TargetTable
には、主キーとして機能するID列があり、さらに[KeyPart_1, KeyPart_2]
列タプルに一意性制約があります。 MyTableType
はTargetTable
と同様のスキーマを持ち、[KeyPart_1, KeyPart_2]
column-Tupleの主キーも定義します。
このMERGE
クエリを実行できるプロセスが1つだけであることを確認しようとしています。SERIALIZABLE
分離レベルがこれを強制していると思いました。ただし、そうではないようです。私はこれらをキャプチャしました XMLログイベント デッドロック中にどのリソースとロックが機能しているかを示します。 1つのクエリには排他ロック(X)があり、もう1つのクエリには更新ロック(U)があります。これを入力していると、UPDATE
句の[KeyPart_1, KeyPart_2]
column-Tupleを更新する必要がないことがわかります。このタプルは、インデックスの更新。
これを解決する方法について他に提案はありますか?私は盲目的にTABLOCKX
をテーブルヒントとして使用することができると思いますが、ここでSERIALIZABLE
分離レベルがどのように失敗したかを理解したいと思います。
ありがとう!
特定の時点でこのMERGE
クエリ(ストアドプロシージャ)の実行を1つのプロセスのみに許可する場合は、 sp_getapplock
が適切です。あいまいなクエリヒントとは対照的に、非常に単純で、理解と保守が簡単です。ヒントで同じ効果を得ることは不可能だと言っているのではありません。単純なミューテックスを理解する方が簡単です。
これは私が使用するストアドプロシージャのテンプレートです。必要に応じてタイムアウトを調整します。呼び出し元はタイムアウトの可能性を認識し、必要に応じて再試行する必要があります。
CREATE PROCEDURE MergeIt
@dataToMerge MyTableType READONLY
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION
BEGIN TRY
DECLARE @VarLockResult int;
EXEC @VarLockResult = sp_getapplock
@Resource = 'MergeIt_app_lock',
@LockMode = 'Exclusive',
@LockOwner = 'Transaction',
@LockTimeout = 60000,
@DbPrincipal = 'public';
IF @VarLockResult >= 0
BEGIN
-- Acquired the lock
MERGE INTO TargetTable WITH(HOLDLOCK) AS [target]
USING @dataToMerge AS [source]
ON [source].KeyPart_1 = [target].KeyPart_1 AND
[source].KeyPart_2 = [target].KeyPart_2
WHEN NOT MATCHED THEN
INSERT(Data, KeyPart_1, KeyPart_2)
VALUES([source].Data, [source].KeyPart_1, [source].KeyPart_2)
WHEN MATCHED THEN
UPDATE SET [target].Data = [source].Data
;
END ELSE BEGIN
-- timeout waiting for the lock
-- TODO: handle the problem, e.g. return some error code,
-- indicating that the caller should retry.
END;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
-- TODO: handle the problem. Return some error code?
END CATCH;
RETURN <the error code>
END