直接查看show engine innodb status\g输出中的latest detected deadlock区块即可准确定位死锁:它完整呈现两个事务的sql、线程id、持锁与等待锁关系、索引名及锁类型;重点分析 (1) transaction和 (2) transaction中的trx_query与trx_mysql_thread_id,结合holds the lock(s)和waiting for this lock to be granted判断锁冲突根源。

怎么看懂 SHOW ENGINE INNODB STATUS 里的死锁现场
直接执行 SHOW ENGINE INNODB STATUS\G,输出里只要找到 LATEST DETECTED DEADLOCK 这一段,就说明你拿到了真实死锁快照。别跳过、别只扫 SQL 文本——真正关键信息藏在三处:
-
*** (1) TRANSACTION:和*** (2) TRANSACTION:后面的mysql tables in use和LOCK WAIT行,告诉你事务正在操作哪张表、是否卡在等锁 -
HOLDS THE LOCK(S)下列出的具体记录(比如space id 123, page no 456, n bits 72)和索引名(如PRIMARY或idx_user_id),这才是锁住的实际位置 -
WAITING FOR THIS LOCK TO BE GRANTED里的 SQL,要结合它前面的index字段看——如果显示PRIMARY但业务本该走idx_order_status,那根本问题不是死锁,是索引失效
为什么 innodb_print_all_deadlocks 必须开
默认情况下 SHOW ENGINE INNODB STATUS 只保留最后一次死锁,线上如果每小时发生多次,你看到的可能是“过期”现场。临时开启用:SET GLOBAL innodb_print_all_deadlocks = ON;长期要用则必须写进配置文件:
[mysqld] innodb_print_all_deadlocks = 1 log_error = /var/log/mysql/error.log
注意:开启后所有死锁都会追加进 error log,不清理会撑爆磁盘。上线后建议搭配日志轮转或定时归档。
如何从锁类型反推根因
死锁日志里锁描述不是装饰,它直接指向问题类型:
-
RECORD LOCKS space id X page no Y n bits Z→ 纯主键/唯一索引行锁,大概率是多表更新顺序不一致 -
GAP LOCKS或NEXT KEY LOCKS→ 涉及非唯一索引或范围查询,比如WHERE price BETWEEN 100 AND 300 FOR UPDATE,此时插入同区间新数据就会冲突 -
lock_mode X locks rec but not gapvslock_mode X locks gap before rec→ 前者是安全的行锁,后者说明 InnoDB 在防幻读,但也是死锁高发区
别被 “没加索引” 带偏方向
很多同学一看到日志里有 gap before rec 就立刻去加索引,结果死锁还在。因为间隙锁本身是 RR 隔离级别的正常行为,加索引不一定能消除它——除非你把条件改成能命中唯一索引的等值查询。真正要查的是:
- 那个
WAITING FOR的 SQL 是否真的需要FOR UPDATE?能不能用SELECT ... LOCK IN SHARE MODE降级? - 事务里有没有先
SELECT FOR UPDATE再INSERT同一范围的操作?这种组合极易触发间隙锁竞争 - 两个事务更新的行集合是否完全相同?如果是,那顺序不一致就是唯一解;如果不是,得查是不是 WHERE 条件实际扫描了远超预期的行数
死锁不是随机事件,它是并发路径上确定性的循环依赖。日志里每一条锁信息,都在告诉你“谁先拿了什么、谁在等什么”,漏掉任意一项,排查就变成猜谜。











