江门专业制作网站,html网站前台模板,网站建设 jz.woonl,网站开发demo是什么终于来到死锁检查线程的第三步#xff0c;可以解决死锁了。 作者#xff1a;操盛春#xff0c;爱可生技术专家#xff0c;公众号『一树一溪』作者#xff0c;专注于研究 MySQL 和 OceanBase 源码。 爱可生开源社区出品#xff0c;原创内容未经授权不得随意使用#xff0…终于来到死锁检查线程的第三步可以解决死锁了。 作者操盛春爱可生技术专家公众号『一树一溪』作者专注于研究 MySQL 和 OceanBase 源码。 爱可生开源社区出品原创内容未经授权不得随意使用转载请联系小编并注明来源。 本文基于 MySQL 8.0.32 源码存储引擎为 InnoDB。 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.
2024-07-07T13:00:15.602446Z 0 [Note] [MY-012469] [InnoDB] *** (1) TRANSACTION:
TRANSACTION 227599, ACTIVE 21 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1192, 2 row lock(s)
MySQL thread id 8, OS thread handle 123145400471552, query id 96 localhost 127.0.0.1 root statistics
SELECT i1 FROM t1 WHERE id 20 FOR UPDATE
2024-07-07T13:00:15.602597Z 0 [Note] [MY-012469] [InnoDB] *** (1) HOLDS THE LOCK(S):
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
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 0000000a; asc ;;1: len 6; hex 000000035958; asc YX;;2: len 7; hex 82000000a50110; asc ;;3: len 4; hex 80000065; asc e;;2024-07-07T13:00:15.603277Z 0 [Note] [MY-012469] [InnoDB] *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
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
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 00000014; asc ;;1: len 6; hex 000000035958; asc YX;;2: len 7; hex 82000000a5011d; asc ;;3: len 4; hex 800000c9; asc ;;2024-07-07T13:00:15.603950Z 0 [Note] [MY-012469] [InnoDB] *** (2) TRANSACTION:
TRANSACTION 227600, ACTIVE 17 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1192, 2 row lock(s)
MySQL thread id 11, OS thread handle 123145401536512, query id 97 localhost 127.0.0.1 root statistics
SELECT * FROM t1 WHERE id 10 FOR UPDATE
2024-07-07T13:00:15.604083Z 0 [Note] [MY-012469] [InnoDB] *** (2) HOLDS THE LOCK(S):
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
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 00000014; asc ;;1: len 6; hex 000000035958; asc YX;;2: len 7; hex 82000000a5011d; asc ;;3: len 4; hex 800000c9; asc ;;2024-07-07T13:00:15.604741Z 0 [Note] [MY-012469] [InnoDB] *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
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
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 0000000a; asc ;;1: len 6; hex 000000035958; asc YX;;2: len 7; hex 82000000a50110; asc ;;3: len 4; hex 80000065; asc e;;2024-07-07T13:00:15.605401Z 0 [Note] [MY-012469] [InnoDB] *** WE ROLL BACK TRANSACTION (2) 前面带日期和时间的日志只有系统变量 log_error_verbosity 的值为 3才会记录到 MySQL 错误日志文件中。 4. 唤醒死锁受害事务
死锁环中选择出来的受害事务会回滚。回滚操作并不是由死锁检查线程完成而是由事务自己完成。
要想让受害事务自己回滚它得知道自己被选择成为死锁受害事务了这个操作由死锁检查线程完成。
死锁检查线程会给死锁受害事务打个标志让它在被唤醒之后知道自己被选择成为死锁受害事务了。
死锁受害事务进入锁等待状态之前创建了一个锁结构这个锁结构的 type_mode 属性的第 9 位被设置为 1 了表示这个锁结构处于锁等待状态。
现在这个锁结构需要从事务对象的 trx_locks 链表中删除。
如果这个锁结构对应的是行锁还需要从 rec_hash 的数组中对应的行锁结构链表中删除。
如果这个锁结构对应的是表锁还需要从表对象的 locks 链表中删除。
然后死锁检查线程会触发死锁受害事务的等待事件唤醒死锁受害事务。这个等待事件保存在死锁受害事务占用的那个 slot 对应的 srv_slot_t 对象的 event 属性中。
到这里死锁检查线程检查并解决死锁的过程就结束了。
剩下工作就由死锁受害事务自己完成了。
死锁受害事务要完成什么工作
当然是回滚了。
5. 总结
死锁检查线程解决死锁的过程如下
把死锁环中各事务按照进入锁等待状态的先后顺序排好序放到死锁数组中。遍历死锁数组每轮循环取一个事务。第 1 轮循环取死锁数组中第 1 个事务作为候选死锁受害事务。第 2 轮及以后的循环根据事务的优先级、是否改变了不支持事务的表的数据、事务的回滚成本从本轮循环取到的事务和上一轮循环选出来的死锁受害事务两者中选择一个作为本轮循环的受害事务。最后一轮循环选出来的受害事务就是最终的死锁受害事务这个事务会回滚。
选出死锁受害事务之后死锁检查线程还会根据系统变量 innodb_print_all_deadlocks 的值决定是否记录死锁日志。
然后会给死锁受害事务打个标记再唤醒死锁受害事务。
更多技术文章请访问https://opensource.actionsky.com/
关于 SQLE
SQLE 是一款全方位的 SQL 质量管理平台覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库为开发和运维提供流程自动化能力提升上线效率提高数据质量。
✨ Githubhttps://github.com/actiontech/sqle 文档https://actiontech.github.io/sqle-docs/ 官网https://opensource.actionsky.com/sqle/ 微信群请添加小助手加入 ActionOpenSource 商业支持https://www.actionsky.com/sqle