MySQL 核心模块揭秘 | 27 期 | 死锁(3)解决死锁
目录
1. 选择死锁受害事务
2. 计算并更新事务权重
3. 记录死锁日志
4. 唤醒死锁受害事务
5. 总结
正文
1. 选择死锁受害事务
前面介绍了死锁线程做的准备工作,以及发现死锁的过程。现在,是时候解决死锁了。
解决死锁最重要的事情,就是决定回滚死锁环中哪个事务,也就是选择哪个事务作为死锁受害事务。
选择死锁受害事务之前,还要做一件比较重要的小事,就是按照死锁环中各事务进入锁等待状态的时间从先到后进行排序。排序之后的事务,会存放到一个数组里,我们称之为死锁数组。
之所以要这么做,是为了根据其它条件无法选出哪个事务作为死锁受害事务的情况下,选择最晚进入锁等待状态的事务作为死锁受害事务。
给死锁环中各事务排序之后,就可以基于死锁数组来选择死锁受害事务了。
这个过程当然又要遍历死锁数组了,同样,每次取死锁数组中的一个事务。
第 1 轮循环有点特殊,直接把取到的事务(死锁数组中第一个事务)作为候选受害事务。
第 2 轮及以后的循环,把取到的事务和上一轮循环选出来的候选受害事务进行比较,决定两者之中谁作为本轮循环的受害事务。
选择谁作为本轮循环的受害事务,这是个艰难的决定,过程如下。
第 1 步,根据两个事务的优先级,决定谁是本轮循环的受害事务。
两个事务中,如果一个是高优先级事务(优先级大于 0),一个是低优先级事务(优先级等于 0),选择低优先级事务作为本轮循环的受害事务。
如果两个事务都是高优先级事务(优先级大于 0),选择优先级更低的事务作为本轮循环的受害事务。
如果两个事务都是低优先级事务(优先级等于 0),进入第 2 步
。
第 2 步,根据事务是否改变(插入、更新、删除)了不支持事务的表(例如 MyISAM 表)的数据,决定谁是本轮循环的受害事务。
两个事务中,如果只有一个事务改变了不支持事务的表的数据,选择它作为本轮循环的受害事务。
如果两个事务都没有改变,或者都改变了不支持事务的表的数据,进入第 3 步
。
第 3 步,根据事务的回滚成本,决定谁是本轮循环的受害事务。
事务的回滚成本,由两部分相加得到:
- 事务进入锁等待状态之前,产生的 undo 日志数量。
- 事务进入锁等待状态之前,加表锁和行锁总共创建了几个锁结构。
如果两个事务回滚成本不同,选择成本低的那个作为本轮循环的受害事务,否则进入第 4 步
。
第 4 步,选择本轮循环取到的事务作为受害事务。
来到这一步,说明前三步都无法在两个事务中选出一个作为本轮循环的死锁受害事务。
这两个事务是:本轮循环取到的事务、上一轮循环选出来的受害事务。
因为死锁数组中各事务已经按照进入锁等待状态的时间先后排了序,这一步直接把本轮循环取到的事务作为本轮循环的受害事务,其实隐含了一个逻辑,就是选择两个事务中更晚进入锁等待状态的事务,作为本轮循环的受害事务。
遍历完死锁数组中所有事务之后,最终会选出一个事务作为受害事务。
2. 计算并更新事务权重
前面介绍过,在准备工作阶段,死锁线程提升阻塞事务权重时,死锁环中锁等待事务的权重,不会累加到阻塞事务的权重上,而是要等到确定死锁受害事务之后,再为死锁环中除受害之外的其它事务进行一次提升权重的操作。
现在,是时候了。
提升权重的过程,从被死锁受害事务阻塞的那个事务开始,根据死锁环中各事务的等待关系,逐个把锁等待事务的权重累加阻塞事务的权重上。
上面只介绍了提升权重操作,其实还有一个降低权重操作,就是把死锁受害事务的权重降为 0。
以上提升权重、降低权重操作的结果,都临时存放在权重数组里。
完成以上操作之后,死锁环中所有事务的权重都会更新到对应的事务对象中。
3. 记录死锁日志
如果系统变量 innodb_print_all_deadlocks
的值为 ON
,死锁检查线程还会把死锁的详细信息写入 MySQL 的错误日志文件中。
示例 SQL 写入 MySQL 错误日志文件的死锁信息如下:
2024-07-07T13:00:15.602373Z 0 [Note] [MY-012468] [InnoDB] Transactions deadlock detected, dumping detailed information.<br>2024-07-07T13:00:15.602446Z 0 [Note] [MY-012469] [InnoDB] *** (1) TRANSACTION:<br>TRANSACTION 227599, ACTIVE 21 sec starting index read<br>mysql tables in use 1, locked 1<br>LOCK WAIT 3 lock struct(s), heap size 1192, 2 row lock(s)<br>MySQL thread id 8, OS thread handle 123145400471552, query id 96 localhost 127.0.0.1 root statistics<br>SELECT i1 FROM t1 WHERE id = 20 FOR UPDATE<br>2024-07-07T13:00:15.602597Z 0 [Note] [MY-012469] [InnoDB] *** (1) HOLDS THE LOCK(S):<br>RECORD LOCKS space id 0 page no 46 n bits 80 index PRIMARY of table `test`.`t1` trx id 227599 lock_mode X locks rec but not gap<br>Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0<br> 0: len 4; hex 0000000a; asc ;;<br> 1: len 6; hex 000000035958; asc YX;;<br> 2: len 7; hex 82000000a50110; asc ;;<br> 3: len 4; hex 80000065; asc e;;<br><br>2024-07-07T13:00:15.603277Z 0 [Note] [MY-012469] [InnoDB] *** (1) WAITING FOR THIS LOCK TO BE GRANTED:<br>RECORD LOCKS space id 0 page no 46 n bits 80 index PRIMARY of table `test`.`t1` trx id 227599 lock_mode X locks rec but not gap waiting<br>Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0<br> 0: len 4; hex 00000014; asc ;;<br> 1: len 6; hex 000000035958; asc YX;;<br> 2: len 7; hex 82000000a5011d; asc ;;<br> 3: len 4; hex 800000c9; asc ;;<br><br>2024-07-07T13:00:15.603950Z 0 [Note] [MY-012469] [InnoDB] *** (2) TRANSACTION:<br>TRANSACTION 227600, ACTIVE 17 sec starting index read<br>mysql tables in use 1, locked 1<br>LOCK WAIT 3 lock struct(s), heap size 1192, 2 row lock(s)<br>MySQL thread id 11, OS thread handle 123145401536512, query id 97 localhost 127.0.0.1 root statistics<br>SELECT * FROM t1 WHERE id = 10 FOR UPDATE<br>2024-07-07T13:00:15.604083Z 0 [Note] [MY-012469] [InnoDB] *** (2) HOLDS THE LOCK(S):<br>RECORD LOCKS space id 0 page no 46 n bits 80 index PRIMARY of table `test`.`t1` trx id 227600 lock_mode X locks rec but not gap<br>Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0<br> 0: len 4; hex 00000014; asc ;;<br> 1: len 6; hex 000000035958; asc YX;;<br> 2: len 7; hex 82000000a5011d; asc ;;<br> 3: len 4; hex 800000c9; asc ;;<br><br>2024-07-07T13:00:15.604741Z 0 [Note] [MY-012469] [InnoDB] *** (2) WAITING FOR THIS LOCK TO BE GRANTED:<br>RECORD LOCKS space id 0 page no 46 n bits 80 index PRIMARY of table `test`.`t1` trx id 227600 lock_mode X locks rec but not gap waiting<br>Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0<br> 0: len 4; hex 0000000a; asc ;;<br> 1: len 6; hex 000000035958; asc YX;;<br> 2: len 7; hex 82000000a50110; asc ;;<br> 3: len 4; hex 80000065; asc e;;<br><br>2024-07-07T13:00:15.605401Z 0 [Note] [MY-012469] [InnoDB] *** WE ROLL BACK TRANSACTION (2)<br>