Ste*_*idi 5 deadlock merge sql-server-2014
我试图避免我的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
Run Code Online (Sandbox Code Playgroud)
TargetTable具有作为主键的标识列,并且对[KeyPart_1, KeyPart_2]列元组具有唯一性约束。 MyTableType具有类似于列元组的架构,TargetTable并且还定义了一个主键[KeyPart_1, KeyPart_2]。
我试图确保MERGE在任何给定时间只允许一个进程运行此查询,并且我认为SERIALIZABLE隔离级别会强制执行此操作。然而,情况似乎并非如此。我已经捕获了这些XML 日志事件,它们显示了在死锁期间哪些资源和锁在起作用。一个查询有一个排他锁 (X),另一个有更新锁 (U)。当我输入这个时,我看到没有必要更新子句中的[KeyPart_1, KeyPart_2]列元组UPDATE,这很可能会导致死锁,因为该元组将触发索引更新。
关于如何解决这个问题还有其他建议吗?我想我可以盲目地尝试TABLOCKX用作表提示,但我想了解SERIALIZABLE隔离级别是如何在这里失败的。
谢谢!
如果您想确保MERGE在任何给定时间只允许一个进程运行此查询(存储过程),那么这
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
Run Code Online (Sandbox Code Playgroud)