平时遇到的死锁,绝大多数情况下,都可以根据当时的场景进行重现,然后具体分析解决,下文这个死锁几次尝试测试模拟,均没有成功重现
在尝试用profile跟踪加锁顺序之后,大概可以推断到当时死锁发生的原因,但是仍有无法重现,为了避免不必要的麻烦,这里用测试表的方式,尽可能还原尝试的场景,来做进一步的分析。
死锁发生的场景如下(暂不论表设计合不合理,索引合不合理,sql语句写法合不合理,分析死锁是主要目的,解决死锁是另外一回事)
目标表为TestDeadLock,大概结构如下
1,TestDeadLock表为堆表(有自增列的主键,但是主键nonclustered),col2.col3上的索引分别是idx_col2,idx_col3,Col2无重复,Col3上重复值较多,表数据量不会很多,几千行或者几万行的样子,
2,存在并发按照多个不同的Col2字段删除的情况(delete from TestDeadLock where col2 in (x,y,z);)
create table TestDeadLock ( id int identity(1,1) primary key nonclustered, col2 varchar(30), col3 varchar(30), col4 varchar(30) )
1,session1 执行delete from TableA where col2 in (x,y,z);
2,session2 执行delete from TableA where col2 in (l,m,n);
其中,删除的目标列条件 in (x,y,z);与in (l,m,n);中的数据在Col2这个字段的值上无重复,无交叉,但是多个Col2条件上对应的Col3这个字段值是一样的
session1与session2发生死锁,xml_deadlock_report显示session1是持有col2上的key级别的U锁,等待col3上key级别的U锁,session1是持有col3上的key级别的U锁,等待col2上key级别的U锁
如下是xml_deadlock_report锁等待的信息,敏感信息用XXXXXXXXXX和YYYYYYYYYYYYY替代了,其中XXXXXXXXXX类似如上的idx_col3 索引Id,YYYYYYYYYYYY类似于如上的idx_col2索引Id
<resource-list> <keylock hobtid="XXXXXXXXXXXXXX" dbid="6" objectname="" indexname="" id="lock12fe62f80" mode="U" associatedObjectId="XXXXXXXXXXXXXX"> <owner-list> <owner id="process——2" mode="U" /> </owner-list> <waiter-list> <waiter id="process——1" mode="U" requestType="wait" /> </waiter-list> </keylock> <keylock hobtid="YYYYYYYYYYYYY" dbid="6" objectname="" indexname="" id="lock126403100" mode="U" associatedObjectId="YYYYYYYYYYYYY"> <owner-list> <owner id="process——1" mode="U" /> </owner-list> <waiter-list> <waiter id="proces——2" mode="U" requestType="wait" /> </waiter-list> </keylock> </resource-list>
先说我自己的理解:
理论上说,两个delete的session都会走Col2上的索引,两个语句对于其目标数据的加锁顺序是一致的,不会出现死锁的情况,
当然只是臆测,因为sql语句没有加任何锁提示,数据量小的时候,任何一种执行计划都是有可能的。
但是仅仅从死锁的语句,是无法拿到当时的执行计划的,也就无法证实当死锁发生的时候,双方用的哪一种执行计划。
构造测试表以及测试数据,其中:对于col3,尽管重复值非常多,仍然有一个索引(再次说明,这里暂抛开索引合不合理,语句合不合理这一说)
create table TestDeadLock ( id int identity(1,1) primary key nonclustered, col2 varchar(30), col3 varchar(30), col4 varchar(30) ) create index idx_col2 on TestDeadLock(col2) create index idx_col3 on TestDeadLock(col3) declare @i int = 0 while @i<200000 begin insert into TestDeadLock values (concat('X0000000000',@i),cast(rand()*10 as int),'test') set @i = @i+1 end
测试表的索引对象Id
以delete from TestDeadLock where col2 in ( 'X00000000003','X000000000020')为例,这里先拿到其伪列Id