背景说明:

表结构和索引情况:

 

update delete走索引吗 update select_主键

 

 

update delete走索引吗 update select_聚集索引_02

 

 

select操作语句和执行计划如下: select  [DL_ID] ,[Name],Age from TT_DeadLock where Name ='xxxxxxxx'

 

update delete走索引吗 update select_聚集索引_03

 

 

update操作语句和执行计划如下::update TT_DeadLock set Name= 'jack1',[LastModifier]='chenzk' ,[LastModifyTime]=GETDATE() where DL_ID='0001FC1D-6DA9-4EDE-91BD-571158477BB3';

 

update delete走索引吗 update select_主键_04

 

   

验证执行:

背景交代完毕,现在我们分别循环的执行以上两个操作,很快就会发生死锁,如下图:

 

update delete走索引吗 update select_update delete走索引吗_05

 

 

额外说明:左侧窗口是模拟高频执行update操作,右侧窗口模拟高频执行select操作。其中存储过程定义如下:

ALTER PROC [dbo].[GetAgeByNameProc] @NameStr varchar(100) AS

select  [DL_ID] ,[Name],Age from TT_DeadLock where Name =@NameStr

  

分析原因:

既然确实发生了死锁,那说明存在两个操作互相等待对方持有的锁。

  我们先分析Select操作:根据索引和执行计划可以看到,先执行非聚集索引seek,由于select的Age列没有在idx_Name中覆盖,因此要执行key lookup获取Age列。这两步操作隐含了2把锁:idx_Name的S锁和key looup操作中聚集索引的S锁。

  然后在看Update操作:由于是update操作因此显然会对主键聚集索引增加X锁,但是这里由于更新的列Name恰好也包含在索引idx_Name中,因此就需要更新idx_Name索引的内容,也就是说也要对idx_Name增加一把X锁。

  说清楚了以上两个操作的内部细节,我们将其格式化看看:

 

Update操作

Select操作

备注

第一步

更新主键聚集索引--加X锁

seek普通索引idx_Name--S锁

 

第二步

更新普通索引idx_Name--加X锁

key lookup操作(主键索引seek)--S锁

 

第三步

执行结束commit

执行结束commit

 

  根据以上表格的情况,可以清楚的看到:当并发执行双方操作同时进入第二步,就出现了互相等待的情况:Update操作等Select释放idx_Name的S锁、Select操作等Update释放主键聚集索引的X锁。

 

解决办法:

思路就是:打断其中任何一个环节,避免互相等待。

方案一:优化idx_Name索引,实现Age列覆盖

方案二:Update操作和Select操作的执行时增加程序锁,将并发强制改为串行执行。

方案三:Select操作增加with(nolock)【对select数据要求不严格时可用,因为会产生脏数据的问题】