必须立刻执行SHOW ENGINE INNODB STATUS\G,因死锁日志仅驻留内存、不落盘、仅保留最后一次;\G强制垂直格式化输出,使HOLDS与WAITING锁信息分行列出,便于交叉比对还原死锁闭环。

必须立刻执行 SHOW ENGINE INNODB STATUS\G,否则死锁现场就没了——它只存内存、不落盘、仅保留最后一次。
为什么SHOW ENGINE INNODB STATUS\G必须加\G?
不加 \G 时,整个输出是单行长文本,HOLDS THE LOCK(S) 和 WAITING FOR THIS LOCK TO BE GRANTED 挤在一行里,根本没法比对锁的交叉关系。加了 \G 才能按字段换行,一眼看出事务 (1) 持有什么、事务 (2) 等什么。
-
\G是 MySQL 客户端特有格式化指令,不是 SQL 语法,不能写成WHERE或其他语句里 - 如果用程序调用(如 Python 的
mysql-connector),\G无效,得自己解析长文本或改用performance_schema.data_lock_waits - 输出中出现大量乱码或截断?检查客户端字符集,优先设为
utf8mb4
死锁日志在哪找?只盯 LATEST DETECTED DEADLOCK
整个命令输出几千行,但真正有用的只有一页:搜索 ------------------------ LATEST DETECTED DEADLOCK ------------------------。它下面只有两个事务块,标号 (1) 和 (2) 不代表执行顺序,只是方便区分。
- 没搜到这段?说明:刚发生的死锁已被新死锁覆盖;或根本没发生死锁(可能是锁等待超时,错误码是
1205而非1213) - 别在
TRANSACTIONS段反复翻找“活跃事务”——那反映当前状态,和已回滚的死锁无关 - 时间戳如
2026-08-11 23:45:22 0x7f是死锁发生时刻,不是命令执行时刻
怎么从日志里还原死锁闭环?
核心动作是交叉比对:事务 (1) 的 HOLDS THE LOCK(S) 是否正好是事务 (2) 的 WAITING FOR THIS LOCK TO BE GRANTED,反之亦然。
- 重点看锁描述里的三要素:索引名(如
index `idx_user_id` of table `app`.`orders`)、页号(page no 7)、锁模式(如lock_mode X locks rec but not gap) - 遇到
lock_mode X locks gap before rec要特别小心——这是间隙锁,常见于WHERE status > 1或唯一键冲突检查,不锁具体行,却能拦住插入,隐蔽性强 - 日志里显示锁在
index `idx_email`,但业务 SQL 是WHERE name = ??基本是索引失效:字段类型不匹配、用了函数(如UPPER(email))、或OR导致全表扫描 - 看到
GEN_CLUST_INDEX?说明表没定义主键,InnoDB 自建隐式聚簇索引,赶紧跑SHOW CREATE TABLE确认结构
查到 SQL 为什么还是修不好?
日志里的 SQL 是事务“最后执行的一句”,但它是否真走索引、有没有触发锁升级,必须结合执行计划验证。
- 用
EXPLAIN FORMAT=TRADITIONAL重跑那条 SQL,确认key字段是否命中预期索引 -
INSERT INTO ... SELECT场景下,日志可能只显示INSERT,但实际锁由SELECT部分引发,需分开分析 - 事务里混用 DML 和 DDL?比如先
UPDATE再ALTER TABLE,DDL 会隐式提交事务,破坏原子性,容易诱发死锁 - 想长期捕获所有死锁?开
SET GLOBAL innodb_print_all_deadlocks = ON,日志会写进 MySQL 错误文件,但别长期开着——磁盘和 I/O 压力会上升
死锁不是慢,是卡死;不是配置问题,是并发逻辑冲突。最常被忽略的点是:以为看到 SQL 就等于复现了路径,其实锁的起点往往在上一条语句、甚至事务开启后的第一个查询里。


















