直接查看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 ------------------------。别去翻 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 = ?,参数值不会展开。
- 别只看 SQL 文本,要结合表结构判断:那个
WHERE条件有没有走索引?没走索引就会升级成全表扫描 + 表级锁,死锁概率陡增 - 如果日志里出现
gap lock或next-key lock,说明涉及范围查询(如WHERE status IN ('pending', 'processing')),间隙锁扩大了冲突面 -
WE ROLL BACK TRANSACTION (1)这行明确标出 MySQL 选的“牺牲者”,它通常是 undo log 更小、回滚代价更低的那个事务
如何避免查不到死锁记录
默认情况下 MySQL 只保留最后一次死锁记录,SHOW ENGINE INNODB STATUS 只能看到最近一次;而生产环境往往连续发生多次,旧的就被覆盖了。
- 临时开启:
SET GLOBAL innodb_print_all_deadlocks = ON(重启失效) - 永久生效:在
my.cnf的[mysqld]段加innodb_print_all_deadlocks = 1,并确认log_error路径可写 - 注意:开启后所有死锁都会追加进 error log,不要长期开着却不清理日志,否则磁盘可能被撑爆
真正难的不是找到日志,而是把 HOLDS 和 WAITING FOR 两行串起来画出等待环——A 持 idx_user_id 锁、等 PRIMARY 锁;B 持 PRIMARY 锁、等 idx_user_id 锁。这种顺序不一致,才是死锁反复发生的根因。


















