mysql默认仅保留最近一次死锁日志,需立即执行show engine innodb status查看;开启innodb_print_all_deadlocks可将所有死锁写入错误日志;云数据库推荐使用控制台“死锁分析”页签快速定位。

直接看 SHOW ENGINE INNODB STATUS 最新死锁日志
MySQL 默认只保留**最近一次**死锁的完整现场,不存历史记录。所以出问题后第一反应就是立刻执行:
SHOW ENGINE INNODB STATUS\G然后在输出里搜
LATEST DETECTED DEADLOCK —— 这段内容会覆盖之前所有死锁日志,稍慢一步就丢了。
常见错误是等业务报错后再去查,结果发现日志已更新为下一次死锁(甚至没再发生),导致“明明出了错却看不到现场”。建议在监控告警触发时,立刻人工或脚本拉取该命令结果。
- 仅对当前连接生效,无需权限,但需有
PROCESS权限(RDS 通常默认开放) - 输出中事务 ID(如
TRANSACTION 12345)、持有锁(HOLDS THE LOCK(S))、等待锁(WAITING FOR THIS LOCK)三块必须对照着看 - 注意末尾那句被回滚的 SQL,它不是“罪魁祸首”,而是压垮骆驼的最后一根稻草——真正的问题藏在两个事务访问资源的**顺序差异**里
让所有死锁都落盘:开启 innodb_print_all_deadlocks
靠手动抓 SHOW ENGINE 太被动,尤其线上偶发、低频死锁根本来不及响应。必须改配置,把每次死锁都写进错误日志:
在 my.cnf 的 [mysqld] 段加上:
innodb_print_all_deadlocks = ON<br>log_error = /var/log/mysql/error.log
- 重启 MySQL 生效(RDS 可通过参数模板修改,无需重启)
- 日志会追加到
log_error指定路径,用grep -A 20 -B 5 "Deadlock" /var/log/mysql/error.log快速提取 - ⚠️ 别忽略磁盘空间:高频死锁时日志增长快,建议配合 logrotate 或云服务的日志轮转策略
从日志定位“谁在锁谁”:看 space id + page no + index
死锁日志里这类行最烧脑也最关键:
RECORD LOCKS space id 88 page no 7 index `idx_account`它不告诉你哪条数据,但给出了精确物理位置:
-
space id对应表空间(一般等于INFORMATION_SCHEMA.INNODB_TABLES.TABLE_ID) -
page no是页号,结合index名可反查冲突行所在索引的范围 - 例如两个事务分别卡在
idx_name和PRIMARY上,基本能断定是“先更新非主键索引,再删主键行” vs “先删主键行,再更新非主键索引”这种经典反序操作
真要精确定位某一行?用 SELECT * FROM table WHERE id = ? 查主键值不靠谱——因为锁可能落在间隙(gap lock)上。更稳的方式是用 SELECT * FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE WHERE SPACE = 88 AND PAGE_NUMBER = 7(需开启 innodb_buffer_page 监控,生产慎用)。
云数据库别只盯命令:善用控制台“死锁分析”页签
华为云 RDS、GaussDB、TaurusDB 等托管服务都内置了可视化死锁分析能力,比手撕日志快得多:
- 进控制台 → 找到目标实例 → “锁&事务” → “死锁分析”页签
- “最近死锁分析”展示最后一次死锁的图形化等待链(事务 A → B → C → A)
- “全量死锁分析”需手动开启开关,支持查最近 7 天全部死锁(注意:未开启前的历史数据不可恢复)
- 关键字段如
happen_time、Object(死锁内容 JSON)、SQL原始语句都直接可点开
容易被忽略的是:这些页面显示的 SQL 是“被回滚的那条”,而真正引发竞争的前置操作(比如一个没加 FOR UPDATE 的 SELECT 引发了隐式锁升级)并不会出现在这里——它只记录最终碰撞点,不记录全过程。










