MySQL 核心模块揭秘 | 26 期 | 死锁(2)发现死锁

目录

  • 1. 一定会检查死锁吗?

  • 2. 找到死锁环

  • 3. 二次确认

  • 4. 总结

正文

1. 一定会检查死锁吗?

上一期,我们介绍了死锁检查线程做的一些准备工作。按照故事发展套路,接下来就要顺理成章的进行死锁检查了。

但是,我们不禁还要问一句:一定会进行死锁检查吗?

答案是否定的。

死锁检查线程是否会检查并解决死锁,由系统变量 innodb_deadlock_detect
决定。如果它的值为 ON(默认值),就会检查并解决死锁,值为 OFF,就不需要检查、更不用解决死锁。

如果不需要检查并解决死锁,死锁检查线程做的那些准备工作不就白费了吗?

不白费。

没有了死锁检查,死锁环中最先达到锁等待超时时间的事务,会结束锁等待。

一个事务结束锁等待之后,死锁环中其它事务、死锁环之外的事务,就有机会获得锁。

这些嗷嗷待哺的事务中,谁能获得锁呢?

死锁检查线程的准备工作之一,是给快照数组中各锁等待事务初始化权重,给阻塞事务提升权重,也就是重新计算了各锁等待事务的权重。

重新计算的权重,会影响到这些嗷嗷待哺的事务获得锁的优先程度。

所以,即使不需要检查并解决死锁,死锁检查线程的准备工作也会不白费。

2. 找到死锁环

死锁检查线程在准备工作阶段,得到了锁等待数组,现在可以基于这个数组,去发现死锁了。

发生死锁时,两个或多个事务之间的等待关系形成了一个环,我们称之为死锁环。

发现死锁的过程,就是基于锁等待数组找到死锁环的过程。这个过程会遍历锁等待数组。

遍历过程可能会进行多轮循环,从锁等待数组的第一个单元开始,每轮循环以一个单元为起点,根据数组单元描述的等待关系顺藤摸瓜,找到锁等待路径上的每一个单元。

也就是说,锁等待数组的每个单元,都作为一条锁等待路径的起点。每轮循环遍历一条锁等待路径,看看这条路径是不是形成了环。

如果遍历某条锁等待路径时,从锁等待数组的某个单元开始,又回到了这个单元,就说明存在死锁环,也就发现了死锁。

从上面的介绍可以看到,找到死锁环的过程并不复杂,但是似乎比较抽象。

为了把这个抽象的过程具象化,我们以示例 SQL 的锁等待数组为例,模拟找到死锁环的过程。

开始之前,我们把示例 SQL 和锁等待数组放到这里。

示例 SQL:

-- 会话 1(事务 1)<br>BEGIN;<br>SELECT id FROM t1 WHERE id = 10 FOR UPDATE;<br><br>-- 会话 2(事务 2)<br>BEGIN;<br>SELECT id FROM t1 WHERE id = 20 FOR UPDATE;<br><br>-- 会话 3(事务 3)<br>BEGIN;<br>SELECT i1 FROM t1 WHERE id = 10 FOR UPDATE;<br><br>-- 会话 4(事务 4)<br>BEGIN;<br>SELECT id, i1 FROM t1 WHERE id = 10 FOR UPDATE;<br><br>-- 会话 1(事务 1)<br>SELECT i1 FROM t1 WHERE id = 20 FOR UPDATE;<br><br>-- 会话 2(事务 2)<br>SELECT * FROM t1 WHERE id = 10 FOR UPDATE;<br>