当 where 子句列中没有聚集索引时,并行更新导致死锁

Posted

技术标签:

【中文标题】当 where 子句列中没有聚集索引时,并行更新导致死锁【英文标题】:Parallel updates causing deadlock when no clustered index in where clause column 【发布时间】:2020-05-23 01:46:43 【问题描述】:

我们遇到了这样一种情况,即在同一事务中尝试从两个同时连接更新表两次时发生死锁,并且每次在 SSMS 中的 2 个查询窗口上运行查询时都可以重现死锁。 (AccountId 列是非聚集键)

见下文。

在 AccountId 列上创建集群键后,不再发生死锁。是什么导致了这种行为?

【问题讨论】:

表格有多少行? 大约 50,000 条记录 大概只有一行AccountId = 1000? sorry..我误会了你的问题,删除了我的回复,正好有一行,但是一行有35列,这就是我的意思。 【参考方案1】:

如果没有 AccountId 上的聚集索引和此列上的非聚集索引,SQL Server 必须先锁定索引键,然后再锁定行。

因此第一次更新将成功,更新后您将只有一行锁定在表中。 第二次更新将尝试锁定此行,并将等待第一次更新的锁定释放。它将能够获得索引上的键锁。 第三次更新将尝试锁定索引键,并等待第二次更新释放锁。死锁。

我能够使用下表重现它:

create table test5 (x int,y int)
insert into test5 values (10,15)
GO

insert into test5 values (11,15)
GO 10000

create index ix on test5(x)

select * from test5

begin transaction

update test5
set y = 5
where x = 10

-- wait here

update test5
set y = 5
where x = 10

rollback

【讨论】:

我相信表中需要有一定数量的行才能升级为表锁?我无法复制该问题,但我的 Accounts 表中只有一行,因此更新只需要行锁定 仍然无法复制 200,000 行:s 对不起@Piotr,我仍然无法理解你的解释。为什么第一次更新后该行仍会被锁定?基本上更新已经完成,所以锁可以被释放吧? 对所有更新行的锁定始终保持到事务结束。 有道理。【参考方案2】:

执行计划具有输出基表 RID (Bmk1000) 的非聚集索引查找和使用 RID 查找的 UPDATE 运算符。这就提供了死锁所涉及的两种资源(非聚集索引键和RID对应的基表行)。

事务 1 在非聚集索引键上使用 U 锁定 AccountId = 1000 然后在基表行上获得U 锁,转换 到X 锁定并释放非聚集索引上的U 锁定 钥匙。 X 锁一直保持到事务结束。 事务 2 在非聚集索引键上使用 U 锁定 AccountId = 1000,但在尝试获取基础上的 U 锁定时被阻止 表行按事务 1 的 X 锁。 事务 1 运行它的第二个 UPDATE 并尝试获取 U 锁定 AccountId = 1000 的非聚集索引键。这已经是 由事务 2 持有。死锁。

【讨论】:

以上是关于当 where 子句列中没有聚集索引时,并行更新导致死锁的主要内容,如果未能解决你的问题,请参考以下文章

聚集索引扫描

常用执行计划操作符

SQL中的WHERE子句中为啥不允许应用聚集函数呢?请通俗的解释一下或者谈谈自己的见解!

在聚集字段上使用 WHERE 子句运行查询时,Google BigQuery 聚集表不会减少查询大小

SQL中WHERE 和HAVING的区别

为啥当 WHERE 子句包含参数化值时 SQL Server 使用索引扫描而不是索引查找