delete+insert 引发的死锁问题

问题

当上层业务会短时间内调用两次接口,导致线上报错死锁,报错信息如下:

屏幕快照 2020-07-06 上午10.56.05.png

死锁日志:


屏幕快照 2020-07-06 下午2.11.23.png

db结构

唯一索引 userid+userKey
普通索引 userkey+uservalue

场景还原

updateUniqueClientIdAndUserValue方法里面,一共有三个db操作:①根据userId和userKeys、userValues查询②根据userkey和value删除数据③批量保存或更新key-value信息

可直接定位到删除+insert这部操作的问题;

下面的表格是两个事务中的db操作:

(红色字体为db提示)

![
屏幕快照 2020-07-06 下午2.14.12.png

]


屏幕快照 2020-07-06 下午2.14.12.png

至此,死锁问题复现

分析原因

首先,死锁为什么会产生?死锁的产生需要相互等待资源,相互等待的资源是什么?

了解这个之前,先了解一下delete操作的时候需要获取什么锁。

屏幕快照 2020-07-06 下午2.14.17.png

以及锁之间的兼容关系:

屏幕快照 2020-07-06 下午2.14.22.png

image.png

由此,可以分析出,结合死锁日志,事务1执行delete操作的时候,获取了索引区间(1086,415097555)的gap锁;

事务2执行的时候,也获取了索引区间(1086,415097555)的gap锁;由于gap锁之间相互兼容,到这一步为止是正常执行的;

事务1insert的时候,需要先获取一个插入意向锁(insert intention),由于官方文档解释,插入意向锁被认为是一种gap锁,这两个锁之间不兼容,事务1需要等待事务2释放索引区间(1086,415097555)的gap锁,此时db在等待事务2释放资源,也没有产生死锁;当事务2也执行insert的时候,事务2也需要获取插入意向锁,也要等待事务1释放索引区间(1086,415097555)的gap锁,事务2发生死锁,事务回滚,gap锁资源释放;事务1获取到这个锁,执行成功。

解决办法

可以看出来,主要是因为删除了一条不存在的数据导致的,再删除之前先查询,再删除;

我们可以来走一下先查询再删除的场景:

事务1删除一条数据,受影响1行,或者到了这条记录的锁以及这个索引之前的区间gap锁;事务2也要删除这条数据,需要先获得这条记录的行锁,等待事务1执行insert之后事务提交,释放锁资源;

思考

  1. 线上出现这个死锁的问题时,线下并没有马上复现,因为删除的数据是存在;只复现除了等待锁资源超时
  2. 死锁日志确实也提到了当insert操作时,获取的(1086,415097555)的插入意向锁那边有问题

你可能感兴趣的:(delete+insert 引发的死锁问题)