MySQL 死锁排查与根治:从日志取证到索引优化

举报
yd_262217709 发表于 2026/10/24 16:41:58 2026/10/24
【摘要】 华为云作品展览馆投递实战:GitCode 仓库、SELF_VERIFY 自委托 STS 凭证、DevBridge 隧道、SATA 售罄故障排查与本地突围全记录。

前言

线上数据库每隔几分钟抛一次死锁告警,日志里两个事务交叉等待同一批表资源——这是很多后端同学都遇过的场景。常见的应急动作是调隔离级别、缩短事务,但很多时候告警依旧。这篇就把一套完整的死锁排查路径写清楚:从日志取证、锁定冲突对,到索引优化与加锁顺序的根治手段。

第一步:拿到死锁现场

死锁发生时,InnoDB 会自动回滚代价较小的事务。要看现场,第一反应应该是:

SHOW ENGINE INNODB STATUS\G

输出里的 LATEST DETECTED DEADLOCK 段是重点,包含:

  • 事务 1 持有的锁(HOLDS THE LOCK(S))与正在等待的锁(WAITING FOR)
  • 事务 2 的同样两组信息
  • 每把锁关联的 SQL 语句和 index name

问题是这段信息只保留最近一次死锁,高并发场景下可能被刷掉。更可靠的做法是开启全量死锁日志:

SET GLOBAL innodb_print_all_deadlocks = ON;

之后每次死锁都会写入 error log,时机不再是问题。云上 RDS 同样支持该参数(动态生效),配合日志告警可以形成完整的证据链。

第二步:读懂锁记录

日志里每把锁的记录形如:

lock_mode IX
lock_rec X locks rec but not gap
index name of lock: idx_order_user

两类典型形态:

形态一:两把行锁都带索引名。这是二级索引行锁冲突——事务 A 按索引 1 锁了行 X 再请求行 Y,事务 B 按索引 2 锁了行 Y 再请求行 X,形成环路。这时把日志里的 SQL 逐条 EXPLAIN,重点看执行计划走了哪个索引、扫描范围多大。经常发现的情况是:两个事务用了不同的查询路径(一个走 idx_a,一个走 idx_b),但目标行集有交集,各自按不同顺序加锁。

形态二:出现大量 gap lock 或 table 级锁。通常是 RR 隔离级别下区间锁碰撞,或者 WHERE 条件无索引导致锁范围放大——等价于在扫描路径上加锁,冲突面指数级增长。

第三步:根治手段按优先级排

1. 补对索引(性价比最高)

死锁日志里看到锁落在 PRIMARY、但 SQL 的 WHERE 走不到任何二级索引时,基本可以断定是全表扫描路径加行锁。按业务实际查询条件补联合索引,让每个事务只锁自己真正要动的行,冲突概率骤降。

2. 统一加锁顺序

应用层约定所有事务按相同顺序访问表和行。比如批量更新列表时统一按主键升序处理,交叉等待直接消失。这一条改动成本几乎为零,效果却出奇地好。

3. 缩小事务颗粒度

  • 批量操作拆批,单事务行数控制在几百行以内
  • SELECT ... FOR UPDATE 的 WHERE 带精确索引条件,避免锁到不相关的行
  • 事务里不要夹外部调用(RPC、发消息),把耗时操作挪出事务边界

4. 评估隔离级别

业务不依赖可重复读时,改 READ COMMITTED 能消掉大部分 gap lock 引发的死锁。改之前务必确认业务侧没有幻读依赖(比如对账类逻辑)。

5. 应用层重试兜底

InnoDB 死锁会自动选择牺牲者回滚,应用层捕获 1213 错误码做指数退避重试。这治标不治本,但能保住可用性,配合前面几条根治手段一起上。

第四步:持续观测防复发

调整上线后,用观测手段验证效果:

  • MySQL 8.0+ 查 performance_schema.data_lock_waits,直接看"谁在堵谁"
  • sys.innodb_lock_waits 视图输出格式更友好
  • 云数据库控制台的锁等待指标配告警,死锁率上升能第一时间感知

一个真实案例的排查轨迹

某订单服务死锁告警,日志显示事务 A 持有 idx_user 上的行锁等待 PRIMARY,事务 B 反过来。EXPLAIN 发现 B 的查询 WHERE 条件只命中 idx_status,而该索引区分度极低(状态只有 3 个枚举值),导致扫描范围覆盖大量行。处理方式分两步:给查询条件补 user_id + status 联合索引;批量操作统一按主键升序。上线后死锁告警归零。

多数"调了隔离级别、缩短了事务还是死锁"的案例,最后都指向索引缺失和加锁顺序不统一这两个根因。排查时优先从这两处下手,往往事半功障。

写在最后

死锁不可怕,可怕的是没有证据链的盲调。先 SHOW ENGINE INNODB STATUS / innodb_print_all_deadlocks 固定现场,再从锁记录反推 SQL 与索引,最后用索引优化 + 顺序统一收尾——这套流程走下来,绝大多数死锁都能根治。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。