必须开启innodb_print_all_deadlocks=on并确保log_error路径有效,否则仅能通过错误日志回溯死锁;show engine innodb status只保留最近一次死锁记录,且重启后清空。

死锁日志只显示最后一次,旧记录早被覆盖了
你执行 SHOW ENGINE INNODB STATUS\G 却没看到报错时那个死锁?不是命令错了,是它本来就只存最近一次。InnoDB 内存里的 LATEST DETECTED DEADLOCK 区块是个单条覆盖式缓存——新死锁一发生,旧的就没了。
常见误判场景:
- 应用报错
Deadlock found when trying to get lock后,你隔了几分钟才连上数据库查,期间又发生了别的死锁 - MySQL 服务重启过,内存清空,
SHOW ENGINE INNODB STATUS输出里根本不会有死锁段 - 错误日志路径没配或权限不对,
innodb_print_all_deadlocks = ON开了也白开
必须开启 innodb_print_all_deadlocks=ON 并确认 log_error 路径有效
靠手动查 SHOW ENGINE INNODB STATUS 是被动救火,真正能回溯问题的只有错误日志。这一步不是“建议”,是硬性前提。
检查并操作:
- 登录 MySQL 执行
SELECT @@innodb_print_all_deadlocks;,返回1才算生效 - 确认
log_error变量指向的文件路径可写:SELECT @@log_error;,常见路径如/var/log/mysql/error.log - 修改配置文件
my.cnf的[mysqld]段,加上两行:[mysqld]\ninnodb_print_all_deadlocks = ON\nlog_error = /var/log/mysql/error.log
- 改完必须重启 MySQL 或执行
SET GLOBAL innodb_print_all_deadlocks = ON;(后者仅对新连接生效)
从死锁日志里快速定位冲突 SQL 和加锁顺序
拿到日志后别通读,直接盯住两个事务块里的 query id 行和 HOLDS THE LOCK(S) / WAITING FOR THIS LOCK 三处。
关键动作:
- 在
(1) TRANSACTION块里找query id后面那条 SQL,比如update t set c = c + 1 where id = 2—— 这就是事务 1 实际卡住的语句 - 看同一事务下的
WAITING FOR THIS LOCK,例如RECORD LOCKS space id 50 page no 4 index PRIMARY,说明它在等主键索引上的记录锁 - 切到
(2) TRANSACTION块,同样提取 SQL 和HOLDS THE LOCK(S),如果它持的是index PRIMARY锁,而事务 1 等这个锁,同时事务 2 又在等事务 1 持有的另一个索引锁,循环就成立了 - 注意
WE ROLL BACK TRANSACTION (1)这行,被回滚的通常是 undo log 更小的那个事务,不一定是“先发起”的那个
锁等待超时(Lock wait timeout)不是死锁,别混为一谈
错误信息里带 Lock wait timeout exceeded 的,压根没触发 InnoDB 死锁检测机制,只是等锁太久被主动踢掉。这类问题不能去 LATEST DETECTED DEADLOCK 里翻,得查 information_schema.innodb_trx 和 innodb_lock_waits 表实时抓现场。
区分依据很直接:
- 死锁报错固定是
ERROR 1213 (40001): Deadlock found when trying to get lock - 锁等待超时报错是
ERROR 1205 (40001): Lock wait timeout exceeded,且不会出现在SHOW ENGINE INNODB STATUS的死锁区块里 - 前者由 InnoDB 主动介入、选一个事务回滚;后者是纯等待超时,谁都没被强制终止,只是客户端断开了
真正难缠的往往是那些没留下完整日志、又不频繁发生的死锁——它们通常藏在业务逻辑深处,比如两个事务以相反顺序更新同一组索引,而这种顺序差异,只会在特定参数组合下才暴露出来。











