<p>必须立刻执行SHOW ENGINE INNODB STATUS\G,因死锁日志仅驻留内存且只保留最近一次;快速定位需搜索“------------------------ LATEST DETECTED DEADLOCK ------------------------”,交叉比对(1)(2)事务块中的HOLDS与WAITING行以还原死锁环。</p>

必须立刻执行 SHOW ENGINE INNODB STATUS\G,死锁日志只驻留内存、仅保留最近一次,延迟几秒就可能被覆盖。
怎么快速定位 LATEST DETECTED DEADLOCK 区块
整个输出很长,但真正有用的只有一页。在终端里直接搜索:------------------------ LATEST DETECTED DEADLOCK ------------------------。别翻 TRANSACTIONS 或 SEMAPHORES 段——那些反映当前状态,和已回滚的死锁无关。
常见错误现象:
- 搜不到该区块:说明最近没发生死锁;或刚发生就被新死锁覆盖;或 MySQL 重启过(该信息不持久)
- 用分号结尾执行,输出挤成一行:必须加
\G,否则根本没法读 - 连的是只读从库:
SHOW ENGINE INNODB STATUS在从库上不反映主库死锁,排查前先确认连接目标
怎么看懂事务块里的 HOLDS THE LOCK(S) 和 WAITING FOR THIS LOCK TO BE GRANTED
区块内有两个事务块,分别以 *** (1) TRANSACTION 和 *** (2) TRANSACTION 开头。编号不代表执行先后,只是标记区分。关键要交叉比对:
-
HOLDS THE LOCK(S):这个事务当前「攥着什么」——记下index `xxx`、space id、page no,它锁了哪张表、哪个索引、大概哪几行 -
WAITING FOR THIS LOCK TO BE GRANTED:这个事务「卡在哪」——它等的锁,必须和另一个事务的HOLDS对得上,才构成闭环 - 末尾的
SQL行(如INSERT INTO t ... ON DUPLICATE KEY UPDATE):这是你能映射到业务逻辑的唯一锚点,务必抄下来
容易踩的坑:
- 把
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 = ?,参数值不会展开。
更关键的是:这句 SQL 是否真走索引、是否触发了隐式锁升级,得结合执行计划和表结构判断。
- 如果日志显示锁在
index `idx_email`上,但你的业务 SQL 是WHERE name = ?,大概率是索引失效(字段类型不匹配、用了函数、或OR导致全表扫描) -
INSERT ... ON DUPLICATE KEY UPDATE在 RR 隔离级别下可能同时持 S 锁和 X 锁,日志里出现S locks gap before rec并非异常,而是唯一键检查所需 - RC 隔离级别下也会出现
gap before rec:官方明确说明,它仍用于外键约束和唯一键重复检查
最易被忽略的一点:死锁日志本身不告诉你“谁先执行”,只呈现互锁快照;而真实根因往往藏在两个事务的加锁顺序差异里——比如一个先更新 id=1 再更新 id=2,另一个反着来。这种顺序问题无法从单次日志直接推断,必须结合应用逻辑与慢日志回溯执行路径。


















