要查看最近一次死锁日志,直接执行 SHOW ENGINE INNODB STATUS,重点关注其中 LATEST DETECTED DEADLOCK 区块,它包含事务ID、SQL语句、索引名、锁类型(X/S/GAP/INSERT_INTENTION)及“WAITING FOR”和“HOLDS THE LOCK(S)”关键行,完整呈现死锁循环等待证据。

怎么看最近一次死锁日志?SHOW ENGINE INNODB STATUS 是 MySQL 内置最直接、最权威的死锁现场快照命令,它不依赖外部工具,也不需要提前开启开关——只要死锁发生过,就一定留痕在这里。执行后,结果里只有一段叫 LATEST DETECTED DEADLOCK 的区块是你要盯死的地方,其他全是干扰信息。
- 这个命令输出是实时的,只保留最后一次死锁详情,反复发生多次死锁时,前几次会被覆盖
- 它不会自动刷新,必须手动再执行一次才能看到新死锁(不像日志文件可 tail)
- 输出中事务 ID(如
TRANSACTION 4218567890)、SQL 语句、索引名(如 idx_unique_flow_no)、锁类型(X / S / GAP)全在这一块,缺一不可
TRANSACTION 4218567890)、SQL 语句、索引名(如 idx_unique_flow_no)、锁类型(X / S / GAP)全在这一块,缺一不可 别跳过“WAITING FOR THIS LOCK TO BE GRANTED”那行——它告诉你这个事务卡在哪把锁上;也别漏掉“HOLDS THE LOCK(S)”——它暴露了对方手里攥着什么资源。这两句合起来,就是死锁循环等待的铁证。
为什么光看 SHOW ENGINE INNODB STATUS 不够?
因为它只给“结果”,不给“上下文”。比如你看到事务 A 在等 idx_data_id 上的 X 锁,但不知道它自己刚删了哪几行、有没有扫全表、WHERE 条件是不是用了函数导致索引失效。这时候必须交叉验证:
- 查当前正在运行的事务:
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX,确认事务是否还活着、持续了多久、有没有卡在长事务里
- 查锁等待链:
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS,能直接看到谁在等谁、等的是哪条记录(LOCK_TRX_ID 和 BLOCKING_TRX_ID 对应上)
- MySQL 8.0+ 可查具体加锁行为:
SELECT * FROM performance_schema.data_locks\G,它会列出每一把锁对应的索引、页号、记录值,比老版本的 INNODB_LOCKS(已废弃)更准更细
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX,确认事务是否还活着、持续了多久、有没有卡在长事务里 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS,能直接看到谁在等谁、等的是哪条记录(LOCK_TRX_ID 和 BLOCKING_TRX_ID 对应上) SELECT * FROM performance_schema.data_locks\G,它会列出每一把锁对应的索引、页号、记录值,比老版本的 INNODB_LOCKS(已废弃)更准更细 注意:INFORMATION_SCHEMA.INNODB_LOCKS 在 MySQL 8.0.1 之后已被移除,用它会报错,别踩这个坑。
死锁日志里那些锁类型到底啥意思?
死锁日志里频繁出现的 X、S、GAP、INSERT_INTENTION 不是缩写游戏,它们直接决定你能不能复现和修复:
-
X(排他锁):写操作专属,互斥——别人不能读也不能写同一行
-
S(共享锁):SELECT ... LOCK IN SHARE MODE 加的,允许多个 S 并存,但会阻塞 X
-
GAP(间隙锁):即使隔离级别是 READ-COMMITTED,唯一索引上的重复键检查、外键约束仍会用它,不是“RC 就没 gap 锁”那么简单
-
INSERT_INTENTION(插入意向锁):一种特殊的 gap 锁,多个事务往同一间隙插不同值时,它不冲突;但一旦和已有 X 或 S 锁相遇,立刻排队等
X(排他锁):写操作专属,互斥——别人不能读也不能写同一行 S(共享锁):SELECT ... LOCK IN SHARE MODE 加的,允许多个 S 并存,但会阻塞 X GAP(间隙锁):即使隔离级别是 READ-COMMITTED,唯一索引上的重复键检查、外键约束仍会用它,不是“RC 就没 gap 锁”那么简单 INSERT_INTENTION(插入意向锁):一种特殊的 gap 锁,多个事务往同一间隙插不同值时,它不冲突;但一旦和已有 X 或 S 锁相遇,立刻排队等 常见误判:看到 gap before rec 就以为是范围查询惹的祸,其实很可能是你 INSERT 时触发了唯一约束校验,InnoDB 主动加了 gap 锁防幻读插入——哪怕你只插一条。
业务高峰期死锁频发,怎么快速定位 SQL 问题?
死锁不是随机事件,是并发逻辑+SQL 走向+索引设计三者咬合出的问题。重点盯这三类 SQL:
-
DELETE + INSERT 同表同条件(如先删旧数据再插新数据),极易因加锁顺序不一致形成循环
-
UPDATE 没走索引或走了范围索引(如 WHERE created_at > '2025-01-01'),会锁住一大片间隙
-
SELECT ... FOR UPDATE 或 LOCK IN SHARE MODE 出现在长事务里,把锁持有了几秒甚至几十秒
DELETE + INSERT 同表同条件(如先删旧数据再插新数据),极易因加锁顺序不一致形成循环 UPDATE 没走索引或走了范围索引(如 WHERE created_at > '2025-01-01'),会锁住一大片间隙 SELECT ... FOR UPDATE 或 LOCK IN SHARE MODE 出现在长事务里,把锁持有了几秒甚至几十秒 实操建议:
- 把出问题的 SQL 单独拎出来,在测试库用
EXPLAIN FORMAT=tree看执行计划,确认是否真的命中了预期索引 - 用
BEGIN; [你的SQL]; SELECT * FROM performance_schema.data_locks WHERE ENGINE = 'INNODB'\G观察实际加了哪些锁 - 如果业务允许,把“删+插”改成“ON DUPLICATE KEY UPDATE”或“REPLACE INTO”,从源头避免两阶段加锁
死锁真正难的不是看懂日志,而是把日志里的锁行为,和你自己代码里事务的起止边界、SQL 执行顺序、索引定义一条条对上。少一个环节,分析就断档。


















