如何解决两个表上并发 MERGE 的死锁

use*_*910 4 sql-server deadlock sql-server-2016

最近在尝试同时在两个表上插入数据时,偶尔会遇到死锁。

以下是表结构

Create Table TableA
(
  Id Bigint not null primary key ,
  FieldA1 nvarchar(50)
)
go

Create Table TableB
(
  Id Bigint not null primary key,
  TableAId Bigint not null constraint FK_TableA Foreign Key (TableAId) References TableB(Id),
  FieldB1 nvarchar(50)
)
go

CREATE type TableBParam as table
(
    Id Bigint,
    TableAId Bigint not null,
    FieldB1 nvarchar(50)
)

--Deadlock was observed on the save query
go
CREATE PROC SaveTableB
(  
 @val [dbo].[TableBParam] READONLY  
)  
AS 
BEGIN 
SET NOCOUNT ON;  

MERGE [dbo].[TableB] AS T  
USING (SELECT * FROM @val) AS S  
  ON ( T.Id = S.Id)  
WHEN MATCHED THEN  
    update set FieldB1 = S.FieldB1
WHEN NOT MATCHED THEN 
    insert(TableAId, FieldB1) Values(S.TableAId, S.FieldB1);

END
go
Run Code Online (Sandbox Code Playgroud)

在单个事务中,我试图通过调用各自的存储过程(SaveTableA 和 SaveTableB)来完成 tableA 和 Table B 上的数据插入。SaveTableA 与 SaveTableB 相同。

.死锁图

数据库已启用读提交快照隔离。想知道插入时如何发生死锁?从上图中我推断来自一个线程的 SaveTableB 是受害者,而不同线程上相同过程的其他实例则持有 TableA 主键的锁。

从这个参考我了解到这里的锁在索引的 Btree 上,基于参考https://technet.microsoft.com/en-us/library/ms189849(v=sql.105).aspx和死锁图以上。当锁定的资源位于聚集索引 BTree 节点本身时,

从上面的推论我很好奇理解以下

  1. 如图所示,插入操作是否总是在 BTree 级别保持锁定?
  2. 当死锁中涉及的语句是来自两个不同事务的 SaveTableB 过程时,试图理解为什么 TableA 的聚集索引上有锁?
  3. 由于我在这里使用 Merge 语句,是否假设插入时的 btree 锁会一直保持到事务结束或 Merge 语句结束?

很想知道是否有可能在提到的这种情况下陷入僵局,试图了解如何处理它,以便我准备好解决它。感谢您帮助我了解这一点。

Dav*_*oft 11

MERGE默认情况下,并发语句将死锁或产生 PK 违规,因为 的“扫描”阶段MERGE是在没有限制性锁的情况下执行的。您需要添加一个锁定提示才能使其工作。请参阅为什么 TSQL MERGE 因主键违规而失败?不是原子的吗?由 David Browne 提供MERGE锁定细节。

或者只是像这样修复它:

MERGE [dbo].[TableB] with (serializable) AS T  
USING (SELECT * FROM @val) AS S  
  ON ( T.Id = S.Id)  
WHEN MATCHED THEN  
    update set FieldB1 = S.FieldB1
WHEN NOT MATCHED THEN 
    insert(TableAId, FieldB1) Values(S.TableAId, S.FieldB1);
Run Code Online (Sandbox Code Playgroud)