直接看show engine innodb status的latest detected deadlock区块是定位死锁根因的唯一实时精准入口,它完整呈现两个事务的sql、持锁与等待锁关系、索引名及锁类型;需立即执行该命令并搜索关键词定位,结合explain验证索引使用,交叉比对加锁顺序以确认环路等待。

直接看 SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK 区块,它包含全部关键线索;其他手段(如错误日志、监控)都是辅助,不能替代这个输出。
怎么看懂 SHOW ENGINE INNODB STATUS 里的死锁上下文
执行该命令后,重点只盯 LATEST DETECTED DEADLOCK 部分,它不是日志摘要,而是 InnoDB 检测到死锁瞬间的快照。你需要从中提取三类信息:
- 两个(或多个)事务各自执行的
UPDATE/DELETE语句,注意它们操作的表名、WHERE 条件和字段 - 每个事务「持有」哪些锁(
HOLDS THE LOCK(S))、「等待」哪些锁(WAITING FOR THIS LOCK TO BE GRANTED),锁类型是X(排他)、S(共享)还是GAP(间隙) - 事务的
undo log entries数量——InnoDB 回滚的是 undo 量小的那个,但你得看哪个事务更“轻”,才能判断回滚是否合理
示例中若看到事务 A 持有 users.id=1 的 X 锁、等待 orders.order_id=100 的 X 锁,而事务 B 持有 orders.order_id=100 的 X 锁、等待 users.id=1 的 X 锁,这就是典型的「加锁顺序不一致」——根本原因已定位。
为什么 EXPLAIN 要紧跟着死锁 SQL 一起查
死锁日志里出现的 SQL 如果没走索引,InnoDB 就会全表扫描并逐行加锁,锁范围远超预期,极易与其他事务交叉形成死锁。此时 EXPLAIN 是唯一能验证执行计划的手段:
- 若
type是ALL或key是NULL,说明没走索引,必须补索引 - 若
rows_examined远大于实际匹配行数(比如 WHERE id IN (1,5,100) 却扫描了 10 万行),说明索引失效,常见于对索引字段用了函数(WHERE DATE(create_time) = '2026-07-01')或隐式类型转换(WHERE user_id = '123'但字段是 INT) - 联合索引要检查最左前缀是否被命中:WHERE status = ? AND created_at > ? 应建
(status, created_at),反过来建则无效
如何确认是不是长事务或 ORM 动态拼接惹的祸
死锁日志里如果事务 ACTIVE 时间明显偏长(比如 >1s),或 SQL 中 WHERE 条件字段顺序在不同请求间随机变化(如有时是 WHERE a=1 AND b=2,有时是 WHERE b=2 AND a=1),基本可锁定这两类问题:
- 查
INFORMATION_SCHEMA.INNODB_TRX表,筛选trx_started早于当前时间 2 秒以上的事务,看其trx_query是否含网络调用、sleep、大结果集遍历等 - 在应用层加日志,记录每次生成的 SQL 完整文本(含参数值),比对多条死锁相关 SQL 的 WHERE 字段顺序是否一致;GORM/MyBatis 若未显式指定
@SelectProvider或Ordering,就容易出这个问题 - 禁止在事务内做非 DB 操作——哪怕只是调一次 HTTP 接口,都可能把锁持有时间从毫秒级拉长到秒级
真正难排查的不是死锁本身,而是那些看似无关的细节:比如某次批量更新的 IN 列表没排序,导致 InnoDB 内部加锁顺序与另一处单条 FOR UPDATE 不一致;或者一个本该走索引的查询,因参数类型不匹配悄悄退化成全表扫描。这些点不会报错,但会在高并发时突然爆发死锁。











