MySQL 死锁套路:一次诡异的批量插入死锁问题分析
线上最近出现了批量 insert 的死锁,百思不得解。死锁记录如下:
第一反应是批量 insert,insert 的顺序不一样导致的死锁。但是这个在这里是不成立的。原因有两点
出现问题的批量插入 SQL 中顺序是一模一样的,在顺序一样的情况下,只会进行插入等待(implicit lock 转 explicit X 锁)下面有实验如果是因为批量插入顺序不一致带来的死锁日志,打印的结果不是等待插入意向锁(insert intention waiting),下面有实验
现在采用一个简化的表,做实验
实验 01
在记录不存在的情况下,两个同样顺序的批量 insert 同时执行,第二个会进行锁等待状态首先 truncate t1;
可以看到目前锁的状态
在我们执行事务 t1 的 insert 时,没有在任何锁的断点处出现,这跟 MySQL 插入的原理有关系 insert 加的是隐式锁。什么是隐式锁?隐式锁的意思就是没有锁在 t1 插入记录时,是不加锁的。这个时候事务 t1 还未提交的情况下,事务 t2 尝试插入的时候,发现有这条记录,t2 尝试获取 S 锁,会判定记录上的事务 id 是否活跃,如果活跃的话,说明事务未结束,会帮 t1 把它的隐式锁提升为显式锁(X 锁)
源码如下:
t2 获取 S 锁的结果:DB_LOCK_WAIT
实验 02
批量插入顺序不一致的导致的死锁日志不是等待插入意向锁
到目前为止,已经陷入了僵局,完全没法复现死锁的情况。看了代码,发现在 insert 之前有一个 delete,但是 delete 与 insert 不在一个事务里面,也就是 delete 提交以后,才进行批量 insert,真正出问题的地方在批量 insert 的地方。一开始就排除了 delete 对后面的影响,难道不在一个事务,也会有影响?写了一个代码去模拟,有很大概率会复现
对应的 SQL 如下,注意是两个事务
这个代码在两个线程同时调用的时候,非常容易死锁。后来翻遍了网上相关的死锁案例,有一个关于 purge 删除的过程可能跟这个有关系。
如果标记为删除,说明事务已经提交,还没来得及 purge,这时后面的事务加 S 锁等待;
在源码中打印一些日志。1.在 storage/innobase/row/row0ins.c 的 row_ins_set_shared_rec_lock 增加日志,可以看到对唯一索引增加 S 锁的过程
2.在 lock_rec_enqueue_waiting 增加日志,可以看到锁等待的情况
日志大概如下
其中 2563=2048+512+3=LOCK_INSERT_INTENTION+LOCK_GAP+LOCK_X
这个过程跟非常经典的三个事务同时 insert,一个回滚,剩下的两个事务一个成功,一个死锁,其实是一模一样的原理。
实验 03
三个 insert ignore,一个回滚造成的死锁
insert 语句都是 insert ignore into t1(a, b)values(“1”, “1”);以下省略
死锁日志,跟我们案例中的一模一样
目前来看,得到的结论是:一个已提交但是未 purge 掉的记录会造成后续插入获取 S 共享锁,两个事务同时获取 S 锁,然后尝试获取插入意向锁,造成死锁网上大神梳理的 insert 流程
首先对插入的间隙加插入意向锁(Insert Intension Locks)
如果该间隙已被加上了 GAP 锁或 Next-Key 锁,则加锁失败进入等待;如果没有,则加锁成功,表示可以插入;
然后判断插入记录是否有唯一键,如果有,则进行唯一性约束检查
如果不存在相同键值,则完成插入如果存在相同键值,则判断该键值是否加锁
如果没有锁, 判断该记录是否被标记为删除如果标记为删除,说明事务已经提交,还没来得及 purge,这时加 S 锁等待;如果没有标记删除,则报 1062 duplicate key 错误;
如果有锁,说明该记录正在处理(新增、删除或更新),且事务还未提交,加 S 锁等待;
插入记录并对记录加 X 记录锁;
上面是我 debug 源码得到的一些结论,如果我的理解有误的话,可以留言讨论哦~
看完三件事❤️
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
关注公众号 『 java 烂猪皮 』,不定期分享原创知识。
同时可以期待后续文章 ing🚀
关注后回复【666】扫码即可获取学习资料包
原文作者:挖坑的张师傅
文章出处:https://club.perfma.com/article/2342623
评论