Next-Key Lock 不防止脏读,只在 REPEATABLE READ 级别下通过行锁+间隙锁防止幻读;其生效需满足:当前读、范围条件、有合适索引,否则可能退化为表锁或失效。

Next-Key Lock 不能防止脏读——它根本不是为解决脏读设计的。脏读在 REPEATABLE READ 及以上隔离级别下,靠的是 MVCC + 事务提交可见性判断,而不是锁。
真正需要厘清的是:你实际想防的,大概率是 幻读(尤其是“侧面插入”引发的范围结果不一致),而误用了“脏读”这个术语。
REPEATABLE READ 下为什么不会脏读?
- 脏读只可能发生在
READ UNCOMMITTED级别; -
REPEATABLE READ(MySQL InnoDB 默认)中,普通SELECT是快照读(snapshot read),基于 MVCC 读取事务开始时的一致性视图; - 即使其他事务正在修改某行但未提交,当前事务也看不到——这不是靠
Next-Key Lock挡的,是靠read view判断版本可见性。
所以:
- 不加锁的
SELECT不会脏读,也不用Next-Key Lock; -
Next-Key Lock只在当前读(SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE)时才被触发。
哪些操作会触发 Next-Key Lock 防止“侧面插入”?
“侧面插入”即其他事务在你查询的范围间隙里插入新行,导致再次查询结果集膨胀——这正是幻读。要拦住它,必须满足两个条件:
- 隔离级别是
REPEATABLE READ(默认即可); - 使用当前读语句,且带范围条件(如
BETWEEN、>=、<、IN等)。
例如:
SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE;
该语句会:
- 对所有
age在 [20, 30] 内的现有行加Record Lock; - 对这些行之间的所有间隙(比如
age=22和age=27之间)加Gap Lock; - 合起来就是
Next-Key Lock,阻止其他事务插入age∈ (20, 30) 的新记录。
注意:
- 如果条件是等值且命中唯一索引(如
WHERE id = 100),InnoDB 可能只加Record Lock,不锁间隙; - 如果条件没走索引,会升级为表锁(严重性能风险);
-
INSERT自身也会检查Gap Lock,若间隙被锁,会阻塞或报Lock wait timeout exceeded。
容易踩的坑
- 把
SELECT ... FOR UPDATE写在事务外,或没开启事务(AUTOCOMMIT=1),锁会在语句结束立即释放,起不到保护作用; - 在非主键/非唯一索引字段上做范围当前读,却没建对应索引,导致全表扫描+全表
Next-Key Lock,极易死锁或拖垮并发; - 混淆快照读和当前读:以为加了
FOR UPDATE就“更安全”,但其实它让读变慢、阻塞写,还可能放大锁冲突; - 用
READ COMMITTED隔离级别时,Next-Key Lock不生效(InnoDB 退化为仅Record Lock),幻读风险回归。
Next-Key Lock 的作用边界很清晰:它不碰脏读,只在当前读+范围查询+可重复读级别下,通过锁住“行+间隙”来堵住幻读入口。真正容易被忽略的,是它对索引的强依赖——没索引,它就锁不住“侧面”,反而锁住整张表。


















