MySQL的临键锁Next_Key_Lock使用场景

总结摘要
MySQL的临键锁Next_Key_Lock使用场景

Next-Key Lock(临键锁)是 Record Lock(行锁)Gap Lock(间隙锁) 的组合。 它的锁住范围是:(前一个索引记录, 当前索引记录]。 即:锁住当前记录本身,以及当前记录和前一条记录之间的“空隙”。

它主要在 RR(可重复读)隔离级别 下生效。以下是触发 Next-Key Lock 的具体 SQL 场景:


1. 范围查询 —— 最典型的 Next-Key Lock 场景

当你使用 >, <, >=, <=, BETWEEN 等范围条件进行查询并加锁时,InnoDB 会扫描索引,并对扫描到的每一个索引记录加上 Next-Key Lock。

SQL 示例: 假设数据 id 为:10, 20, 30

1
2
3
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id >= 20 FOR UPDATE;

加锁逻辑(简化版):

  1. 首先找到 id=20:因为是 >=,20 是命中的第一条记录。InnoDB 会对 id=20 加上 Next-Key Lock
    • 锁范围:(10, 20]。既锁住 20 这一行,也锁住 10 到 20 之间的空隙(防止插入 11-19)。
  2. 继续往后找:找到 id=30。也会加上 Next-Key Lock (20, 30]
  3. 最后是 Supremum(最大值):找到正无穷,加上 Gap Lock (30, +∞)

后果:你不能插入 id 在 11-19, 21-29, 31+ 的任何数据。


2. 等值查询 —— 针对“非唯一索引”的命中

这是面试中极易混淆的点。

  • 如果是 唯一索引(Primary Key/Unique Key) 的等值查询命中记录,Next-Key Lock 会退化Record Lock(行锁),因为唯一索引保证了唯一性,不需要锁间隙。
  • 如果是 普通索引(Secondary Index) 的等值查询命中记录,即使只有一条,也会使用 Next-Key Lock

SQL 示例: 表结构:id (主键), age (普通索引)。 数据:id=1, age=10; id=5, age=20; id=10, age=30

1
2
3
-- 事务 A
BEGIN;
SELECT * FROM t WHERE age = 20 FOR UPDATE;

加锁逻辑:

  1. 因为 age 是普通索引,可能有多个 age=20
  2. InnoDB 会定位到 age=20 的索引项。
  3. 对该项加上 Next-Key Lock
    • 锁范围:(10, 20]。即锁住 age=10age=20 之间的空隙,以及 age=20 这一行。
    • 注意:为了防止新的 age=20 插入到现有记录后面,它通常还会向右扫描,直到遇到第一个不满足条件的值(比如 30),然后锁住 (20, 30) 的间隙。

后果:其他事务无法插入 age 在 11-19 之间的记录,也无法插入 age=20 的新记录。


3. 等值查询 —— 针对“未命中”的情况

当查询条件没有匹配到任何行时,即使你是等值查询,InnoDB 也会优化为 Gap Lock(而不是 Next-Key Lock),但这个 Gap Lock 的定位逻辑是基于 Next-Key 的算法来的。

SQL 示例: 数据 id10, 20, 30

1
2
3
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id = 15 FOR UPDATE;

加锁逻辑:

  1. InnoDB 找不到 id=15
  2. 它会根据 B+ 树结构,定位到 id=15 应该在的位置(即 10 和 20 之间)。
  3. 加上 Gap Lock (10, 20)
    • 注意:这里虽然是 Gap Lock,但其算法基础是 Next-Key Lock 的变种(命中记录则锁记录+间隙,未命中则只锁间隙)。

4. 特殊情况:命中“唯一索引”的等值查询(优化)

这一点非常重要,必须单独拎出来说,否则你会误解 Next-Key Lock 的使用范围。

如果执行的是 命中主键或唯一索引 的等值查询:

SQL 示例:

1
2
3
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id = 20 FOR UPDATE; -- id 是主键

加锁逻辑:

  • MySQL 会对 Next-Key Lock 进行优化
  • 因为 id=20 是唯一的,不可能再插入一条 id=20 的数据,所以不需要锁前面的间隙。
  • Next-Key Lock 退化为 Record Lock
  • 锁范围:仅锁住 id=20 这一行。

后果:其他事务完全可以插入 id=15id=25,但修改 id=20 会被阻塞。


总结表

SQL 类型索引类型命中情况加锁类型锁的范围示例
WHERE id = 20唯一索引命中Record Lock20 这一行
WHERE id > 20任意索引范围Next-Key Lock(20, 30] + (30, +∞)
WHERE age = 20普通索引命中Next-Key Lock(10, 20] + (20, 30)
WHERE id = 15任意索引未命中Gap Lock(10, 20)
UPDATE/DELETE同 SELECT同 SELECT同 SELECT同 SELECT

一句话总结: 只要你是在 RR 隔离级别 下执行 SELECT ... FOR UPDATEUPDATEDELETE,且你的 SQL 是 范围查询 或者 命中了非唯一索引的等值查询,MySQL 就会使用 Next-Key Lock 来锁住索引记录及其前方的间隙,从而彻底解决幻读问题。