Bootstrap

MySQL间隙锁死锁问题

一、场景还原

当时同事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 复现

;