next-key lock 是 record lock 与 gap lock 的组合,锁定左开右闭区间 (prev_value, current_record],核心用于解决幻读;其范围依赖索引结构和实时数据分布,非固定“范围锁”。

Next-Key Lock 是什么,为什么不是“范围锁”那么简单
MySQL 的 Next-Key Lock 不是单独一种锁,而是 Record Lock(行锁) + Gap Lock(间隙锁)的组合。它锁定的是「索引记录本身」和「该记录前一个索引值到当前记录之间的间隙」——换句话说,它锁住的是一个左开右闭区间:(prev_value, current_record]。这个设计核心目标是解决幻读,但副作用是:哪怕你只查 WHERE id > 10 AND id ,InnoDB 也可能锁住 <code>id = 20 这条记录(如果它存在),甚至锁住 id = 20 后面的空隙。
哪些查询会触发 Next-Key Lock
关键看是否满足两个条件:使用**可重复读(RR)隔离级别** + **走索引(最好是唯一索引以外的索引)**。主键或唯一索引等值查询(如 WHERE id = 15)只会加 Record Lock;而范围查询、LIKE 前缀匹配、非唯一索引上的等值查询,都会激活 Next-Key Lock。
-
SELECT ... FOR UPDATE或UPDATE/DELETE走非唯一索引时,锁整个扫描范围的 Next-Key 区间 -
WHERE status = 'pending'(status有非唯一索引)→ 锁所有匹配值及其前间隙,可能比你想象的宽得多 -
WHERE created_at > '2024-01-01'→ 锁住第一个大于该时间的记录,以及它前面的间隙,还可能延伸到索引末尾 - 如果查询不走索引(全表扫描),InnoDB 会退化为锁所有聚簇索引记录 + 所有间隙 → 实际上接近表级锁效果
如何验证 Next-Key Lock 正在生效
不能只看 SHOW ENGINE INNODB STATUS 里的 lock_mode X locks rec but not gap 这类描述——它容易误导。真正可靠的方式是构造并发操作并观察阻塞行为:
- 会话 A 执行:
SELECT * FROM orders WHERE amount > 100 FOR UPDATE(amount有普通索引) - 会话 B 尝试插入
amount = 105→ 被阻塞(因为 105 落在某个 Next-Key 区间内) - 会话 B 尝试插入
amount = 50→ 可能成功(除非它刚好掉进另一个已存在的间隙中) - 用
SELECT * FROM performance_schema.data_locks查看LOCK_DATA字段,若显示类似100, 105或105, 110这种成对值,基本就是 Next-Key 区间
常见踩坑点:明明没改数据,为什么还锁表?
最典型的是在非唯一索引上做 UPDATE ... WHERE non_unique_col = ?。即使只命中一行,InnoDB 仍会锁住该值对应的所有可能位置——包括它前面的间隙,防止其他事务插入“相同值”的新行(避免幻读)。这导致看似单行更新,却卡住一批后续插入。
- 误以为
SELECT ... LOCK IN SHARE MODE安全 → 它同样用 Next-Key Lock,只是锁类型是 S 而非 X - 忽略索引选择性:低选择性字段(如
gender)建了索引,WHERE gender = 'M'可能锁住整个索引段 - 在 RR 级别下用
INSERT ... SELECT,源表扫描过程也会加 Next-Key Lock,影响并发度 - 唯一索引失效(比如隐式类型转换)会导致降级为普通索引 → Next-Key Lock 意外激活
Next-Key Lock 的边界依赖于索引结构和现有数据分布,没有预设“范围”,每次执行都得现场计算。这意味着同样的 SQL,在不同数据状态下锁的区间可能完全不同——这点最容易被忽略。










