登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL 死锁日志怎样还原等待关系

来源:17golang原创

时间:2026-10-14 08:56:34 387浏览 收藏

MySQL 死锁日志还原等待关系,关键是把每个事务的“已经持有”和“正在请求”分开,再按锁资源及不兼容的锁模式配对。等待方请求的资源被另一事务持有,才构成一条等待依赖;依赖闭合成环才解释了死锁。不能仅凭两条 SQL 操作同一张表,就认定它们相互阻塞。

下面以 MySQL 8.4 的 InnoDB 文档为基线。SQL 是供读者使用的只读诊断示例,未在本文运行;事务 A、B 和资源 RA、RB 是教学符号,不是真实生产日志。

官方说明:https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlocks-handling.html

先保留完整报告,再填三类信息

最小操作是取得 SHOW ENGINE INNODB STATUS 中的 LATEST DETECTED DEADLOCK 部分。需要同时保存报告时间、事务段、持有锁、等待锁和回滚选择,不能只截取最后一句错误。

-- 读取最近一次被记录的 InnoDB 死锁,注意报告时间不一定是当前故障。
SHOW ENGINE INNODB STATUS;

-- 查看是否开启全部死锁记录,不在排查脚本中擅自修改全局配置。
SHOW GLOBAL VARIABLES LIKE 'innodb_print_all_deadlocks';

最近一次报告可能被后来的死锁覆盖。频繁发生时,可由有权限的运维人员评估开启 innodb_print_all_deadlocks,将每次死锁写到错误日志,完成定位后恢复配置。日志可能含 SQL 和业务值,应限制读取和保留范围,不能把未脱敏原始日志随意发到公共渠道。

先为每个事务填三项:事务标识与线程线索、已持有的锁资源和模式、等待获得的锁资源和模式。报告中的 (1)、(2) 是该报告内的段落编号,不是持久的事务 ID,也不表示谁先执行。当前 SQL 往往只是等待时的语句,之前获取锁的语句需要应用事务记录补全。

按资源与锁模式配对持有方和请求方

把 HOLDS THE LOCK(S) 当作持有信息,把 WAITING FOR THIS LOCK TO BE GRANTED 当作请求信息。对于记录锁,比较表、索引、space id、page no 和 heap no 等信息,并确认锁模式冲突。不能只比较 page no:同一页包含多条索引记录,同号页在不同表空间也不是同一资源。

假定教学报告明确给出下面四项,且 RA 与 RB 都是确定的索引记录,持有与请求均为不兼容的排他记录锁:

事务已持有正在请求等待谁
ARA 的 X 记录锁RB 的 X 记录锁B,因 B 持有 RB
BRB 的 X 记录锁RA 的 X 记录锁A,因 A 持有 RA

于是等待关系是 A 等待 B,B 等待 A。这里的“请求 RB”不能误写成“A 已持有 RB”。报告的回滚选择表示引擎用来打破环的牺牲事务,不是业务责任认定;被回滚的一方也可能遵循了合理逻辑,根因在两条事务路径的组合。

实际关系可能包含更多事务或锁转换。对每一条依赖写清“请求方、阻塞方、资源、请求模式、持有模式”,无法对应的部分标为未确定,不要补猜。原始段落能证明一个等待环,不一定能证明完整的业务访问顺序。

教学事务与持有资源及请求资源的静态结构
结构说明图:持有与请求分别放在独立分组,通过中文关系标签区分。等待方向以正文关系表为准,图中无执行顺序或运行证据。

当前等待用 Performance Schema 补充

若问题仍在发生,可查询 data_lock_waits,REQUESTING_ENGINE_TRANSACTION_ID 对应请求方,BLOCKING_ENGINE_TRANSACTION_ID 对应阻塞方。再用锁 ID 和 ENGINE 连接 data_locks,读取索引与锁模式。下面的 LEFT JOIN 保留等待关系行,即使采样期间部分锁明细已变化,也能看出对应信息为空。

-- 读取当前 InnoDB 数据锁等待;不是历史死锁回放。
SELECT
    w.REQUESTING_ENGINE_TRANSACTION_ID AS waiting_trx,
    w.BLOCKING_ENGINE_TRANSACTION_ID AS blocking_trx,
    r.OBJECT_SCHEMA AS waiting_schema,
    r.OBJECT_NAME AS waiting_table,
    r.INDEX_NAME AS waiting_index,
    r.LOCK_TYPE AS requested_type,
    r.LOCK_MODE AS requested_mode,
    r.LOCK_DATA AS requested_data,
    b.LOCK_MODE AS blocking_mode,
    b.LOCK_DATA AS blocking_data
FROM performance_schema.data_lock_waits AS w
LEFT JOIN performance_schema.data_locks AS r
    ON r.ENGINE = w.ENGINE
   AND r.ENGINE_LOCK_ID = w.REQUESTING_ENGINE_LOCK_ID
LEFT JOIN performance_schema.data_locks AS b
    ON b.ENGINE = w.ENGINE
   AND b.ENGINE_LOCK_ID = w.BLOCKING_ENGINE_LOCK_ID
WHERE w.ENGINE = 'INNODB';

一项请求可能被多个持有者阻塞,不能强制当作一对一关系。Performance Schema 的线程 ID 也不能直接等同于客户端连接 ID;关联会话时需要正确映射线程信息。LOCK_DATA 只是一项资源线索,可能包含索引键或为空,不是所有场景都提供完整业务行。

死锁处理后,相关等待通常已经消失,因此查询为空不能否定历史死锁。反过来,看到锁等待也不能单独证明死锁环。实时等待、历史死锁报告和应用事务日志要分别注明采样时间,不能拼成同一个瞬间的完整现场。字段文档:https://dev.mysql.com/doc/refman/8.4/en/performance-schema-data-lock-waits-table.html。

历史死锁报告和当前等待表的证据范围
结构说明图:死锁报告绑定持有段和请求段,当前等待绑定等待表和锁明细。两类资料各有时间范围,不能当成历史回放截图。

记录锁、间隙锁不能混成一个行号

InnoDB 的记录锁锁定索引记录。locks rec but not gap 表示该锁不覆盖前方间隙;gap before rec 描述的是记录前方的间隙;next-key 则涉及记录及其前方间隙。insert intention 又有自己的等待含义。资源名字看似相近,冲突规则并不相同。

特别是纯间隙锁之间可以共存,不能仅因两个事务都显示 gap,就画出相互阻塞。需要结合插入意向、记录锁以及请求模式判断。遇到 supremum 这类标记,也不能把它解释成业务表里名叫 supremum 的一条真实行。

PHYSICAL RECORD 的字段数、十六进制值和 heap no 不是可直接照搬的业务列。聚簇索引可能包含内部字段,二级索引记录也有不同结构;需要结合建表定义、索引组成和记录格式解释。heap no 尤其不是主键 ID。锁类型细节可查 https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html。

把等待关系转成修复清单

还原后先检查两条业务路径是否反向访问同一组资源。若 A 路径先锁 RA 后锁 RB,B 路径反过来,可评估统一访问顺序;但 SQL 书写顺序不是访问路径的全部,索引选择、触发器、外键与批量扫描也需要考虑。缩短事务、减少无关扫描、避免持锁等待外部服务,通常比盲目延长超时时间更针对问题。

收到死锁回滚后,应用应按完整事务边界重试,而不是只重新执行最后一条 SQL。重试需要次数上限、退避和幂等保护;事务外的付款、消息等副作用不能不加判断地再执行。锁等待超时的回滚范围与死锁并不总相同,不能共用一个粗略的错误处理假设。

现有证据可得结论仍需补充
匹配资源和不兼容锁模式确定等待依赖此前如何获得锁
回滚事务标记确定引擎选择的牺牲方业务根因与影响
当前等待表有记录存在当前数据锁阻塞是否形成环及其历史
只看到当前 SQL知道等待时在做什么完整事务路径与索引资源

两个事务访问不同业务行为何也会死锁?

业务行不等于完整锁资源。二级索引、范围间隙、约束检查等也可能参与冲突;应以报告实际索引及锁模式为线索。

把隔离级别改成 READ COMMITTED 就没事吗?

不能保证。它可能改变部分锁行为,但死锁不只来自间隙锁。应评估业务一致性要求,不能把降低隔离级别当作默认修复。

为什么日志只给出一部分持有锁?

死锁报告用于说明相关冲突,并不是完整事务历史或全部持锁清单。缺少先前语句时,需要关联应用日志;不能从缺失信息推断“事务没有其他锁”。

最小可复用配方就是:保存同一份完整报告,列事务,分持有与请求,匹配资源及锁模式,再把请求方与阻塞方逐条记录。没有证据的边保留空白,通常比一张看似完整但方向错误的等待图更有用。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>