死锁是事务加锁顺序冲突的必然结果,非数据库故障;需聚焦show engine innodb status\g中“latest detected deadlock”段,分析两事务id、sql、持有锁与等待锁,结合innodb_print_all_deadlocks持久化日志及information_schema.innodb_lock_waits实时监控定位根因。

死锁不是数据库故障,而是事务加锁顺序冲突的必然结果;SHOW ENGINE INNODB STATUS\G 是唯一能直接看到“谁卡了谁”的实时诊断入口。
怎么看懂 SHOW ENGINE INNODB STATUS\G 里的死锁段
执行后输出很长,但只需聚焦 ------------------------ LATEST DETECTED DEADLOCK ------------------------ 这一段。它不是日志摘要,而是 InnoDB 在检测到死锁瞬间抓取的完整现场快照。
关键信息分三块:
-
*** (1) TRANSACTION和*** (2) TRANSACTION:两个互锁事务的 ID、活跃时长、线程 ID(MySQL thread id)、正在执行的 SQL(query id后面那行) -
HOLDS THE LOCK(S):该事务当前已成功加上的锁,包括索引名(index PRIMARY或index idx_status)、锁模式(lock_mode X是排他锁,S是共享锁)、锁类型(rec but not gap是记录锁,gap before rec是间隙锁) -
WAITING FOR THIS LOCK TO BE GRANTED:该事务卡在哪儿——它想加什么锁、加在哪条索引、等待哪个事务释放
注意:WE ROLL BACK TRANSACTION (1) 表示事务 (1) 被选为牺牲者,不是因为它写得错,而是它的 undo log 更小、回滚代价更低。
为什么只看一次 SHOW ENGINE INNODB STATUS 常常不够
该命令只保留最近一次死锁记录,高频死锁场景下极易被覆盖。线上一旦发现 Deadlock found when trying to get lock 报错,必须立刻执行,否则线索丢失。
更稳妥的做法是提前开启持久化记录:
- 运行
SET GLOBAL innodb_print_all_deadlocks = ON;(MySQL 5.6.2+ 支持),之后每次死锁都会追加进 MySQL 错误日志(路径由log_error配置项指定) - 用
grep -i "deadlock" /var/log/mysql/error.log批量提取历史死锁,比人工盯命令行可靠得多 - 注意:该设置不生效于已连接的会话,新连接才生效;且重启 MySQL 后会恢复默认关闭,建议写入
my.cnf持久化
从日志反推业务代码问题的实操路径
死锁日志里暴露的是数据库行为,真正要改的是应用层逻辑。重点比对两事务的 SQL 序列和加锁顺序:
- 检查是否多表操作顺序不一致:比如事务 A 先
UPDATE user再UPDATE order,事务 B 反过来,就极易形成环路等待 - 检查 WHERE 条件是否走了索引:如果日志中
index显示的是PRIMARY但 SQL 是WHERE name = 'xxx',说明name缺少索引,InnoDB 退化为全表扫描加锁,锁范围过大 - 检查是否混用范围查询与插入:如事务中先
SELECT ... FOR UPDATE WHERE id > 100(触发临键锁),再INSERT INTO ... VALUES (105, ...),另一个事务若同时在插 104 或 106,就可能因间隙锁冲突死锁 - 检查 ORM 自动生成 SQL 的确定性:MyBatis 的
<foreach></foreach>循环拼条件、GORM 的链式调用,若未强制排序 ID 列,批量更新时加锁顺序随机,高并发下就是死锁温床
别忽略锁等待的实时状态
SHOW ENGINE INNODB STATUS 只反映“已发生的死锁”,而 information_schema.innodb_lock_waits 能看到“正在发生的阻塞”——这是抢在死锁发生前干预的关键窗口。
执行以下语句可定位当前谁在卡谁:
SELECT r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_thread,
r.trx_query AS waiting_query,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_thread,
b.trx_query AS blocking_query
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
若发现某事务长时间处于 LOCK WAIT 状态(trx_started 时间很老),可直接 KILL 其 blocking_thread 强制释放锁,避免演变成死锁。但这只是临时止血,根因仍需回到事务逻辑和索引设计上。
真正难的从来不是看懂日志,而是把 WAITING FOR THIS LOCK 和业务代码里那行 updateOrderStatus() 函数调用对应起来——锁在数据库里,根子在代码里。











