遇到 MySQL 死锁问题如何解决?
终于来到死锁检查线程的第三步,可以解决死锁了。
> 作者:操盛春,爱可生技术专家,公众号『一树一溪』作者,专注于研究 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 0 0: 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 0 0: 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 0 0: 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 0 0: 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 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。
✨ Github:https://github.com/actiontech/sqle
📚 文档:https://actiontech.github.io/sqle-docs/
💻 官网:https://opensource.actionsky.com/sqle/
👥 微信群:请添加小助手加入 ActionOpenSource

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
链路级资损防控之资损字段防控实践|得物技术
一、背景 资损防控是业务稳定性保障的重要一环,资损防控的核心主要有三点:事前规避、事中发现和事后应急。在资损事前规避方面,商家业务从业务场景入手,进行各业务模块的资损场景的梳理,将最容易出现资损的场景梳理出来。但是这些资损场景的梳理是依赖人去梳理,非常依赖梳理者的个人经验和对业务、链路、系统架构的熟悉程度,这样的梳理方式一定会存在资损场景被遗漏的情况。我们希望能够在人为梳理的基础之上增加系统自动识别能力来对资损场景进行补齐。 因此,希望通过分析测试环境数据库写操作涉及的字段和数据,得到所有字段后,通过AI大模型判断字段是否存在资损风险的方式进行预标记,研发测试进行二次打标并和已有资损场景、资损字段结合,形成业务域资损字段,进而结合公司资损管理平台,精准测试平台能力建立一套基于资损字段->资损方法->调用接口->资损场景->资损布防->布防演练为一体的链路级资损防控方案,提升整体资损场景覆盖度,降低资损风险。 二、应用实践 资损字段梳理 人工梳理 资损字段的梳理主要通过两种方式,一种是通过判断业务数据库字段的数据错误是否会涉及到资损风险,对商家业务所有数据库...
- 下一篇
Zulip Server 8.5 发布,开源团队协作工具
Zulip 是一个开源团队协作工具,一款专为实时和异步对话而设计的现代团队聊天应用程序,支持快速搜索、拖放文件上传、图像预览、组私人消息、可听通知、错过电子邮件消息提醒与桌面应用等。 Zulip Server 8.5 现已发布,一些更新内容包括: 通过删除 Apache Arrow apt 存储库作为依赖项,修复了安装/升级 Debian 系统时出现故障的问题,该存储库每年都会出现 GPG 签名过期的问题。 改进了 Ubuntu 版本升级文档。 修复了在 zulip 用户有权限写入/etc/zulip/zulip-secrets.conf但无权限写入父目录的情况下,manage.pyregister_server--rotate-key在未写入 secrets 的情况下崩溃的问题。 修复了在 S3 请求中错误转发客户端提供的 HTTP authentication headers 导致认证错误的问题。 删除了 Gitter 数据导入工具(Gitter 不再以其支持的格式导出数据)。 升级了 Python 依赖项。 更新了翻译。 更多详情可查看发布说明。
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果