一、场景还原
当时同事A在线上代码中使用了Mybatis-plus的如下方法
com.baomidou.mybatisplus.extension.service.IService saveOrUpdate(T, com.baomidou.mybatisplus.core.conditions.Wrapper<T>)
该方法先执行了update操作,如果更新到就不再执行后续操作,如果没有更新到,才进行主键查询,查询到了就修改,未查询到就新增。具体方法如下
/** * <p> * 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法 * 此次修改主要是减少了此项业务代码的代码量(存在性验证之后的saveOrUpdate操作) * </p> * * @param entity 实体对象 */ default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) { return update(entity, updateWrapper) || saveOrUpdate(entity); }
那么这个方法的做法,为什么会导致间隙锁死锁呢?咱们一起来分析并还原间隙锁死锁的场景。
二、什么是间隙锁
间隙锁是MySQL行锁的一种,与行锁不同的是间隙锁可能锁定的是一行数据,也可能锁住一个间隙。锁定规则如下:
-
当修改的数据存在时,间隙锁只会锁定当前行。
-
当修改的数据不存在时,间隙锁会向左找第一个比当前索引值小的值,向右找第一个比当前索引值大 的值(没有则为正无穷),将此区间锁住,从而阻止其他事务在此区间插入数据。
三、间隙锁的作用
与行锁(例如乐观锁高级实现,MVCC)组合成Next-key lock,在可重复读这种隔离级别下一起工作避免幻读。
四、如何关闭间隙锁(强烈不建议关闭)
1、降低隔离级别,例如降为提交读。
2、直接修改my.cnf,将开关,innodb_locks_unsafe_for_binlog改为1,默认为0即开启
五、还原线上间隙锁死锁的场景
5.1 复现