必须立刻执行show engine innodb status\g,因死锁日志仅驻留内存且只保留最近一次,延迟即被覆盖;需立即搜索“------------------------ latest detected deadlock ------------------------”定位快照,其中(1)(2)事务块的holds the lock(s)与waiting for this lock to be granted须交叉比对,结合sql、索引名、space id及page no还原循环等待闭环。

必须立刻执行 SHOW ENGINE INNODB STATUS\G,死锁日志只驻留在内存中且仅保留最近一次,延迟几秒就可能被覆盖——这不是建议,是硬性前提。
怎么快速定位 LATEST DETECTED DEADLOCK 区域
整个输出很长,但真正有用的只有一页:用文本搜索直接跳转到 ------------------------ LATEST DETECTED DEADLOCK ------------------------。它不是时间线,而是两个事务互锁的快照,编号 (1) 和 (2) 仅为区分,不表示执行顺序。
如果没搜到这一段,说明:最近没发生死锁;或刚发生就被新死锁覆盖;或 MySQL 重启过(该信息不持久)。
别在 TRANSACTIONS 或 SEMAPHORES 部分反复翻找——那些反映的是当前状态,和已回滚的死锁无关。
如何从日志里提取关键 SQL 和锁关系
重点盯住每个事务块里的两行:HOLDS THE LOCK(S) 和 WAITING FOR THIS LOCK TO BE GRANTED。它们必须交叉比对,才能还原死锁环。
- 在
*** (1) TRANSACTION:块中,向下找query id行(不是MySQL thread id),它后面跟着的就是正在执行的原始 SQL,例如:update users set status = 2 where id = 105 - 看同一事务块里的
HOLDS THE LOCK(S):,它告诉你这个事务手里攥着什么资源,比如index PRIMARY of table `test`.`users` + lock_mode X locks rec but not gap,说明它持有一条主键记录的排他锁 - 再看
*** (2) TRANSACTION:块里的WAITING FOR THIS LOCK TO BE GRANTED:如果它等的正是上面那个索引和页号,就构成闭环 - 注意
lock_mode X locks gap before rec这类描述——这是间隙锁,常见于WHERE a > 5或唯一键冲突检查,比行锁更隐蔽、更容易引发并发死锁
为什么看到 SQL 却还是没法复现或修复
日志里的 SQL 是事务“最后执行的一句”,但它是否真走索引、是否触发了隐式锁升级,得结合执行计划和表结构判断。
- 如果日志显示锁在
index `idx_email`上,但你的业务 SQL 是WHERE name = ?,大概率是索引失效(比如字段类型不匹配、用了函数、或OR导致全表扫描) - 没定义主键的表,InnoDB 会建隐式聚簇索引
GEN_CLUST_INDEX,日志里出现这个名,别慌,去查SHOW CREATE TABLE确认 -
INSERT ... ON DUPLICATE KEY UPDATE在 RR 隔离级别下会同时持有 S 锁(做唯一键检查)和 X 锁(更新),日志里可能只显示其中一句,但实际加锁动作是复合的
为什么 INNODB_LOCK_WAITS 在 MySQL 5.7 中不可靠
INNODB_LOCK_WAITS 在 MySQL 5.7 中字段精简、关联不稳定,经常查不到完整链路,甚至返回空。不能单独依赖它定位阻塞源头。
- 它不记录 GAP 锁的等待链,当死锁由插入意向锁与间隙锁循环等待引发时,
INNODB_LOCK_WAITS可能为空或只显示单向等待 -
blocking_trx_id字段在 5.7 中可能为空,或指向一个早已结束但未清理干净的trx_id - 更可靠的做法是:先从
INNODB_TRX拿到waiting_trx_id,再手动去INNODB_LOCKS查它等待的锁,然后根据lock_space+lock_page+lock_rec去反推被哪个事务持有的同位置锁
最易被忽略的是:死锁日志里所有锁描述都带物理定位(space id、page no、heap no),这些才是真实冲突点;光看 SQL 和索引名,容易误判为“逻辑冲突”,其实只是两个事务恰好落在同一个页的不同记录上,而页内间隙重叠导致了插入意向锁竞争。











