| 导语记录一次于2023年01月23日遇到的死锁问题。
READ COMMITTED:只能读取已经提交的数据;此时:允许幻读和不可重复读,但不允许脏读,所以RC隔离级别要求解决脏读;
REPEATABLE READ:同一个事务中多次执行同一个select,读取到的数据没有发生改变;此时:允许幻读,但不允许不可重复读和脏读,所以RR隔离级别要求解决不可重复读;
任何数据库的锁,都是先确定范围,再确定加锁方式的,DML的类型将直接影响到锁的效果。
在Mysql5.7版本官方文档下,有对 间隙锁GapLock 有这样一段陈述(见下图)。
Gap locking can be disabled explicitly. This occurs if you change the transaction isolation level to READ COMMITTED
or enable the innodb_locks_unsafe_for_binlog
system variable (which is now deprecated). In this case, gap locking is disabled for searches and index scans and is used only for foreign-key constraint checking and duplicate-key checking.
大意就是,间隙锁能够被直接明确禁用。比如将事务隔离改为RC或修改系统变量(innodb_locks_unsafe_for_binlog
),这样能够在搜索和索引扫描禁用掉间隙锁GapLock;But,在外键约束和唯一键时会触发使用。
2、背景
从腾讯云给的结论看,死锁原因是TX1(已经持有了Next_key锁)和TX2(申请某个记录锁),两者出现了锁等待,进而导致TX2被回滚了。
解读
解读
死锁原因一目了然
事务B因为在申请锁的路上,所以在本事务结束之前,是不会把已经持有S锁释放掉的;
事务A则因为申请了事务B执行路上,用GapLock赋予了周围记录S锁,导致自己申请周围记录X锁失败了。
批量插入的数据量,控制在2~5条,避免概率性出现的死锁对业务造成的影响持续扩散。
批量插入的异步线程之间,通过线程休眠的方式,既能降低并发insert操作的概率,也能降低Mysql-Server负载;
降低重复数据的并发插入,哪些已经持久化的数据,就过滤掉无需再插入;
解决方案在技术上并不复杂,只需要把发生死锁的唯一索引替换成普通索引就可以了,但是要注意这种替换操作对业务的影响。
参考了腾讯云给的一个文章
从一般的角度来考虑,这个额外的 S 锁似乎是不必要的,所以仔细搜索一下 MySQL bug 的信息,发现一个远古时代的 bug 单:Unexplainable InnoDB unique index locks on DELETE + INSERT with same values (https://bugs.mysql.com/bug.php?id=68021)中也描述了同样的问题,后来官方尝试进行了“修复”,不过之后又非常戏剧性的把这个“修复”给修复掉了:Duplicates in Unique Secondary Index Because of Fix of Bug#68021(https://bugs.mysql.com/bug.php?id=73170)。
MySQL案例:insert死锁与唯一索引(https://cloud.tencent.com/developer/article/2017355?areaSource=&traceId=)