直接看show engine innodb status\g中的“latest detected deadlock”段即可快速定位死锁,需立即执行以防被覆盖;重点比对两个事务的holds the lock(s)与waiting for this lock,结合锁类型、索引及sql分析加锁顺序冲突,并通过开启innodb_print_all_deadlocks=on实现全量日志持久化。

直接看 SHOW ENGINE INNODB STATUS\G 输出里的 “LATEST DETECTED DEADLOCK”
这是最快速、最可靠的现场死锁信息来源,无需额外配置就能拿到最近一次死锁的完整链条。但注意:INNODB STATUS 只保留最后一次死锁记录,新死锁会覆盖旧的——所以一旦发现报错,立刻执行该命令,别等。
执行后重点扫三块内容:
-
*** (1) TRANSACTION和*** (2) TRANSACTION:分别对应两个冲突事务,记下它们的trx_id和活跃时间 -
HOLDS THE LOCK(S):每个事务当前持有哪些锁(比如RECORD LOCKS space id 88 page no 7 index `idx_account`) -
WAITING FOR THIS LOCK:每个事务卡在哪条语句、等待哪个索引/页上的什么类型锁
典型线索是“事务 A 持有 idx_account 锁,等待 PRIMARY 锁;事务 B 持有 PRIMARY 锁,等待 idx_account 锁”——这就是交叉加锁导致的环路。
永久记录所有死锁:必须开 innodb_print_all_deadlocks = ON
靠 SHOW ENGINE INNODB STATUS 查漏补缺不现实,线上环境必须开启全量死锁日志落盘。只改一行配置:
[mysqld] innodb_print_all_deadlocks = ON log_error = /var/log/mysql/error.log
重启 MySQL 后,所有死锁都会追加到错误日志里,不会丢失。验证是否生效:
SHOW VARIABLES LIKE 'innodb_print_all_deadlocks'; —— 返回 ON 才算成功。
常见坑:log_error 路径权限不对会导致写入失败,但 MySQL 不报错;建议用 ls -l /var/log/mysql/ 确认 mysql 用户有写权限。
从日志定位具体 SQL 和表结构
死锁日志里每条 query id 对应一条触发锁的语句,但注意:它只显示最后执行的那条 SQL,不是整个事务的全部操作。要还原逻辑,得结合以下信息:
-
space id+page no→ 对应information_schema.INNODB_SYS_TABLES查表名,再查INNODB_SYS_INDEXES确认索引名 -
index PRIMARY或index `idx_name`→ 直接告诉你哪条索引被锁住,检查该索引是否覆盖查询条件(比如WHERE user_id = ?却只在status上建了索引,就会走全表扫描+间隙锁) - 事务中出现
LOCK WAIT+ 多个lock struct(s)→ 很可能涉及范围查询或未命中索引,触发了间隙锁(Gap Lock),这是 RR 隔离级别下死锁高发区
别只盯着 SQL 文本,重点看它实际走了哪个索引、锁住了哪些数据页——这才是冲突根源。
别忽略隔离级别和事务边界
死锁不是孤立事件,它暴露的是业务代码里隐含的并发风险。两个高频诱因:
- 事务粒度太大:一个事务里串行执行
UPDATE t1→UPDATE t2→INSERT t3,而另一事务按相反顺序操作,就极易形成环路。拆成更小、更专注的事务,或统一访问顺序(比如总是先操作用户表、再订单表) - 隔离级别设为
REPEATABLE-READ(MySQL 默认):会启用间隙锁,让看似无关的范围查询也互相阻塞。如果业务能接受READ-COMMITTED,关掉间隙锁能大幅降低死锁概率(但需确认幻读是否可接受)
查当前级别:SELECT @@transaction_isolation;;改临时级别:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;。别盲目调低,先测业务影响。
真正难的从来不是看懂日志,而是把日志里那两行 SQL 放回真实业务流里,想清楚“为什么它们会以这个顺序、这个索引、这个隔离级别同时跑起来”。日志只给结果,根因永远藏在代码和表设计里。











