大招落地:MySQL 插入更新死锁源码分析
今天再来分析一个死锁场景。下面开始真正的内容。
建表语句:
表中有一条初始化数据:
数据库隔离级别:RC
两条 insert,两条 update
事务 1 和事务 2 语句一毛一样,都是下面这样:
代码的逻辑大概如下,先插入,如果有冲突则更新
死锁条件的过程如下
事务 1:
加锁情况,对 uk 加 S 锁,如下:
![](https://static001.geekbang.org/infoq/45/4507fbe8230828bbf08b7fd0ebd5130b.png)
事务 2:
加锁情况,对 uk 加 S 锁,如下:
![](https://static001.geekbang.org/infoq/d5/d5bc7b18fb97c054edfcad4ad75fb90a.png)
事务 1:
对 uk 加 X 锁,因为事务 2 获取了 S 锁,进入锁等待
![](https://static001.geekbang.org/infoq/d5/d537a8e46527c5518f721d7727f3863c.png)
事务 2:
同样想对 uk 加 X 锁,死锁条件产生:事务 2 拿到了 S 锁,想加 X 锁,事务 1 拿到了 S 锁,也想加 X 锁,彼此都在等对方的 S 锁。
![](https://static001.geekbang.org/infoq/bf/bf9c48e48e82145696591ddfcab6b6c9.png)
这种情况是最简单的,如果只是这么简单,我就不会写了,哈哈,下面来看第二种情况。
一条 insert,两条 update
第一步:事务 1,插入唯一键冲突
第二步:事务 2
第三步:事务 1
出现:事务 2 死锁
分析过程如下:
事务 1
对 uk 加 S 锁,这个没有什么歧义。
![](https://static001.geekbang.org/infoq/45/4507fbe8230828bbf08b7fd0ebd5130b.png)
接下来事务 2
这个对 ux 加 X 锁,进入锁等待状态,这个也没有什么问题。
![](https://static001.geekbang.org/infoq/d0/d09971a81e47ab828a58586be95e58dc.png)
接下来,事务 1 执行 update,情况就复杂很多了,也是想获取 X 锁,但是没有那么顺利。
进入死锁检测流程,重点代码在lock_deadlock_occurs()函数,最近会进入 lock_deadlock_recursive()递归调用函数。
![](https://static001.geekbang.org/infoq/84/84efd6f0604284030474ec5f0b5c8822.png)
-
start 表示顶层调用该函数的事务指针,比如现在正在执行的事务 1 就是 start
-
wait_lock 表示想要获取的锁,这里是事务 1 对 uk 的 X 锁。
-
trx 等待锁的事务指针
死锁的本质是:在递归过程中,如果冲突出现的锁事务id等于顶层事务id(lock_trx == start),则说明有环,就发生死锁。
![](https://static001.geekbang.org/infoq/8d/8d71b1b58da7ad95265cdba255d8f1f0.png)
以下记事务 1 为 t1,事务 2 为 t2
第一次递归
wait_lock 属于 t1 的 lock_X,就是 t1 update 想获取的 X 锁
![](https://static001.geekbang.org/infoq/1a/1a961233fd7ae766d953744f1a5dad6d.png)
这个时候会检查记录上所有的锁,第一个锁是 t1 事务的 S 锁,第二个锁是 t2 事务等待状态的 X 锁
检查第一把锁,t1 事务的 S 锁,因为与 wait_lock 属于同一个事务,没有冲突,继续检查第二把锁。
![](https://static001.geekbang.org/infoq/11/11d1957e4211580e1829a57ebe69fa06.png)
检查第二把锁,是 t2 事务处于等待状态的 X 锁,是互斥的,而且 t2 的 X 锁是处于等待状态的,开始第二次递归调用,检查 t2 的 X 锁,查看它在等待什么锁。
![](https://static001.geekbang.org/infoq/f9/f914110c5f0f3872a932a0dbdfb9bd35.png)
第二次递归
此时传入的 start 没变,wait_lock 变为了 t2 的 X 锁,也就是把 t2 的 X 锁拿出来检测,看跟现有锁有哪些依赖。
t2 的 X 锁在等待 t1 的 S 锁,lock_trx 等于 start,成环死锁产生。
![](https://static001.geekbang.org/infoq/62/629a76aab1f098c7df9568e36e6e616d.png)
也就是:t1 的 insert 插入加了 S 锁,t2 的 X 锁虽然没加成功,但是真实存在,标记为等待状态。t1 再想获取 X 锁,发现与 t2 等待状态的 X 锁冲突。再次检测,发现 t2 等待状态的 X 锁与 t1 的 S 锁冲突,死锁产生。
我画了一个图方便你理解:
![](https://static001.geekbang.org/infoq/cf/cf1f01b6a2df82a86e59a67bc50aebfe.png)
后记
死锁分析是比较复杂的,调试源码可以比较清晰的理清思路,上面是我调试源码的一些结论,如果有理解有误的地方,记得及时帮我指出。
看完三件事❤️
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
-
点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
-
关注公众号 『 java烂猪皮 』,不定期分享原创知识。
-
同时可以期待后续文章ing🚀
![](https://static001.geekbang.org/infoq/34/34172ad7f3cc8e0f28bd1fc6ca2d2b68.png)
作者:挖坑的张师傅
出处:https://club.perfma.com/article/1982680