直接查看SHOW ENGINE INNODB STATUS\G输出中的LATEST DETECTED DEADLOCK区块即可准确定位死锁,它是两个事务互锁的实时快照,需立即执行搜索定位,否则被新死锁覆盖;重点分析(1)(2)事务块中的HOLDS THE LOCK(S)与WAITING FOR THIS LOCK TO BE GRANTED关系、锁类型、索引名及末尾SQL语句。

直接看 SHOW ENGINE INNODB STATUS\G 输出里的 LATEST DETECTED DEADLOCK 区块,就能定位死锁根源——它不是日志汇总,而是两个事务互锁的实时快照,必须立刻查,否则被覆盖。
怎么快速定位 LATEST DETECTED DEADLOCK 区块
整段输出很长,但真正有用的只有一小块。执行命令后,立刻在终端里搜索:------------------------ LATEST DETECTED DEADLOCK ------------------------。别翻 TRANSACTIONS 或 SEMAPHORES 部分——那些是当前状态,和已回滚的死锁无关。
常见错误现象:
- 搜不到该区块:说明最近没发生死锁,或刚发生就被新死锁覆盖,或 MySQL 重启过(该信息不持久)
- 用普通
;结尾执行,输出挤成一行:必须加\G格式化,否则根本没法读 - 连的是只读从库:
SHOW ENGINE INNODB STATUS在从库上不反映主库死锁,排查前先确认连接目标
怎么看懂 (1) 和 (2) 事务块里的锁关系
区块内有两个事务块,分别以 *** (1) TRANSACTION 和 *** (2) TRANSACTION 开头。编号不代表执行先后,只是标记区分。关键要交叉比对:
-
HOLDS THE LOCK(S):这个事务当前「攥着什么」——记下index `xxx`、space id、page no,它锁了哪张表、哪个索引、大概哪几行 -
WAITING FOR THIS LOCK TO BE GRANTED:这个事务「卡在哪」——它等的锁,必须和另一个事务的HOLDS对得上,才构成闭环 - 末尾的 SQL 行(如
UPDATE users SET status = 2 WHERE id = 105):这是你能映射到业务逻辑的唯一锚点,务必抄下来
容易踩的坑:
- 把
MySQL thread id 12当成事务 ID —— 它只是连接线程号,和死锁无关 - 忽略
lock_mode X locks gap before rec这类描述:这是间隙锁,常见于WHERE a > 5或唯一键冲突检查,比行锁更隐蔽 - 看到
GEN_CLUST_INDEX就懵:说明表没显式主键,InnoDB 用了隐式聚簇索引,赶紧查SHOW CREATE TABLE
为什么日志里的 SQL 看起来对不上业务代码
日志中显示的 SQL 是事务「最后卡住的那句」,不是第一句,更不是完整上下文。ORM(如 MyBatis)生成的预编译语句只显示 WHERE id = ?,参数值不会展开。
- 查
INFORMATION_SCHEMA.INNODB_TRX补全上下文:SELECT trx_id, trx_state, trx_started, trx_query FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_id IN ('12345','12346'); - 如果日志锁在
index `idx_email`,但你的业务 SQL 是WHERE name = ?,大概率是索引失效(字段类型不匹配、用了函数、或OR导致全表扫描) -
query id字段常为空,别依赖它找 SQL;转而盯事务块底部紧挨着Trx has been waiting的那行真实语句
怎么确认死锁是否真被解决
日志末尾一定有明确声明:*** WE ROLL BACK TRANSACTION (1)。这表示 InnoDB 已主动回滚事务 (1),不是数据库故障,而是正常机制在起作用。
真正要盯的是:回滚后,同一组 SQL 是否还在高频报错 ERROR 1213 (40001): Deadlock found when trying to get lock。如果持续发生,说明根因未除——大概率是事务加锁顺序不一致(比如 A 先改 orders 再改 users,B 反过来),而不是“运气不好”。修复动作不是加重试,而是统一所有路径的操作顺序。


















