SHOW ENGINE INNODB STATUS\G 是查看最近一次死锁的唯一即时命令,需搜索“LATEST DETECTED DEADLOCK”定位快照;查历史死锁必须提前开启 innodb_print_all_deadlocks=1,否则错误日志无详情。

死锁发生后,SHOW ENGINE INNODB STATUS\G 是唯一能立刻看到完整现场的命令,它不依赖配置、不需重启,但只保留最后一次记录;想查历史死锁,必须提前开启 innodb_print_all_deadlocks = 1,否则错误日志里根本不会写死锁详情。
直接看最近一次死锁:用 SHOW ENGINE INNODB STATUS\G
这不是“备选方案”,而是第一反应动作。执行后别扫全文,直接搜索 LATEST DETECTED DEADLOCK 这一行——它不是摘要,是事务被回滚那一刻的精确快照。
- 常见错误:用
;结尾导致输出挤成一行,必须加\G才能分行可读 - 搜不到该区块?说明最近没发生死锁,或刚发生就被新死锁覆盖,或你连的是只读从库(该命令只反映当前实例的死锁)
- 关键字段要对照着看:
HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED必须成对出现,才构成循环等待证据 - 末尾的 SQL 语句(比如
UPDATE t SET x=1 WHERE id=123)是回溯代码的唯一锚点,别忽略它后面的lock_mode X locks gap before rec这类描述——间隙锁比行锁更容易引发交叉冲突
查所有历史死锁:必须开 innodb_print_all_deadlocks = 1
MySQL 默认关闭全量死锁日志,innodb_print_all_deadlocks = OFF 是出厂设置。不开这个,错误日志里只有启动/崩溃信息,死锁细节压根不落盘。
- 确认是否生效:
SELECT @@innodb_print_all_deadlocks;返回1才算开启 - 临时开启(重启失效):
SET GLOBAL innodb_print_all_deadlocks = ON; - 永久生效(生产必须):在
my.cnf的[mysqld]段加innodb_print_all_deadlocks = 1,然后重启或热加载(RDS 可走参数模板) - 别忘了检查日志路径权限:
SHOW VARIABLES LIKE 'log_error';查到路径后,确认 MySQL 进程用户(如mysql)对该文件有写权限
从错误日志里提取死锁块:别搜 LATEST DETECTED DEADLOCK
这个字符串只存在于 SHOW ENGINE INNODB STATUS 的输出中,错误日志里根本没有。开了 innodb_print_all_deadlocks 后,每次死锁会往 log_error 文件追加一段以 *** (1) TRANSACTION: 开头的完整块。
- 错误匹配:
grep "LATEST DETECTED DEADLOCK" /var/log/mysql/error.log—— 一定为空 - 正确匹配:
grep -A 50 -B 5 "RECORD LOCKS.*space id" /var/log/mysql/error.log—— 因为每段死锁日志必含RECORD LOCKS和具体space id - 注意磁盘空间:开启后所有死锁都追加进 error log,长期开着却不清理,可能撑爆磁盘
锁类型描述直接指向根因,别只盯着 SQL
死锁日志里的锁描述不是装饰,它明确告诉你冲突类型:
-
lock_mode X locks rec but not gap→ 主键/唯一索引上的精准行锁,大概率是多表更新顺序不一致(比如事务 A 先改orders再改users,事务 B 反过来) -
lock_mode X locks gap before rec→ 间隙锁,通常由非唯一索引 + 范围查询触发(如WHERE price BETWEEN 100 AND 300 FOR UPDATE),此时插入同区间新数据就会卡住 - 看到
gap before rec就去加索引?不一定管用。间隙锁是 RR 隔离级别下防幻读的正常行为,根因常是业务逻辑没控制好范围查询粒度,或没统一加锁顺序
最易被忽略的一点:SHOW ENGINE INNODB STATUS 输出不持久,MySQL 重启就清空,新死锁会覆盖旧记录。生产环境必须搭配脚本自动捕获(比如 tail -f error.log | grep deadlock 触发时立刻 dump status),否则等你手动执行,现场早就没了。


















