lock_mode字段表示事务对索引记录施加的锁请求类型,区分已持有锁(HOLDS)与等待锁(WAITING),常见值如X(纯排他锁)、S(共享锁)、X locks gap before rec(间隙锁)等,语义严格且与操作场景强相关。

lock_mode 字段到底表示什么
lock_mode 是死锁日志里最常被误读的字段之一,它不直接告诉你“这个锁是干什么用的”,而是描述“当前事务对某个索引记录施加了哪种类型的锁请求”。关键点在于:它既可能是已持有的锁(HOLDS THE LOCK(S)),也可能是正在等待的锁(WAITING FOR THIS LOCK TO BE GRANTED),而同一行日志中出现两次 lock_mode 很可能含义完全不同。
常见 lock_mode 值及实际含义
MySQL 8.0 死锁日志中出现的 lock_mode 值不是任意组合,而是有明确语义的固定字符串。重点关注以下几种:
-
lock_mode X:纯排他锁,不带 gap,也不带插入意向;通常出现在主键或唯一索引等值查询 +FOR UPDATE或UPDATE/DELETE场景下 -
lock_mode X locks rec but not gap:和上面等价,只是更显式强调“只锁记录、不锁间隙” -
lock_mode S:共享锁,常见于SELECT ... LOCK IN SHARE MODE,或唯一约束冲突时的校验锁 -
lock_mode S waiting:事务在等待获取一个 S 锁,但该锁被其他事务的 X 锁阻塞 -
lock_mode X locks gap before rec:间隙锁(gap lock),不锁记录本身,只锁前后两个值之间的空隙;典型场景是范围条件WHERE c1 > 5 AND c1 下的 <code>UPDATE -
lock_mode X insert intention:插入意向锁,本质是特殊的 gap lock,用于并发插入时协调;它和普通 gap lock 兼容,但会被 record lock 或 S lock 阻塞
为什么同一个 lock_mode 会同时出现在 HOLDS 和 WAITING 里
这是 MySQL 8.0 死锁日志最易引发困惑的地方——比如你看到:
*** (1) HOLDS THE LOCK(S): RECORD LOCKS ... lock_mode X locks rec but not gap *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS ... lock_mode X locks rec but not gap waiting
表面看锁模式一样,其实完全不是一回事:
- HOLDS 行的
lock_mode X locks rec but not gap是事务 1 已成功加在某条记录上的锁(比如id=5) - WAITING 行的
lock_mode X locks rec but not gap waiting是事务 1 正试图加在另一条记录上的锁(比如id=10),但被事务 2 持有的同类型锁堵住了 - 真正构成死锁的,是事务 1 持有 A 记录锁、等待 B 记录锁,而事务 2 持有 B 记录锁、等待 A 记录锁
所以不能只比对 lock_mode 字符串是否相同,必须结合 INDEX_NAME、LOCK_DATA 和上下文中的 HOLDS/WAITING 位置来交叉判断。
容易忽略的 lock_mode 细节
很多排查者卡在“明明看到 X 锁,却找不到谁在改这条记录”,问题往往出在没注意这些细节:
-
lock_mode X出现在index PRIMARY上,不代表 SQL 里写了主键条件——可能是二级索引更新触发的聚簇索引隐式加锁 -
lock_mode S出现在唯一索引上,大概率是 INSERT 唯一冲突导致的校验锁,不是业务主动加的 - 没有
waiting后缀的lock_mode行,不一定就是“已生效”的锁;它只是 InnoDB 在死锁检测快照时刻认为该锁结构存在,但可能正处在锁队列中尚未授予 - MySQL 8.0 日志里不再显示
lock_mode X next-key lock这种写法,统一拆成locks rec but not gap+ 单独的locks gap before rec两行,要看全
真正定位到哪条 SQL 触发了哪个 lock_mode,得回溯事务开头的 SQL(日志里 query id 和 MySQL thread id 可关联),再结合执行计划确认走的是哪个索引。


















