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。
加锁逻辑(简化版):
- 首先找到 id=20:因为是
>=,20 是命中的第一条记录。InnoDB 会对id=20加上 Next-Key Lock。- 锁范围:
(10, 20]。既锁住 20 这一行,也锁住 10 到 20 之间的空隙(防止插入 11-19)。
- 锁范围:
- 继续往后找:找到
id=30。也会加上 Next-Key Lock(20, 30]。 - 最后是 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。
加锁逻辑:
- 因为
age是普通索引,可能有多个age=20。 - InnoDB 会定位到
age=20的索引项。 - 对该项加上 Next-Key Lock。
- 锁范围:
(10, 20]。即锁住age=10到age=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 示例:
数据 id:10, 20, 30。
加锁逻辑:
- InnoDB 找不到
id=15。 - 它会根据 B+ 树结构,定位到
id=15应该在的位置(即 10 和 20 之间)。 - 加上 Gap Lock
(10, 20)。- 注意:这里虽然是 Gap Lock,但其算法基础是 Next-Key Lock 的变种(命中记录则锁记录+间隙,未命中则只锁间隙)。
4. 特殊情况:命中“唯一索引”的等值查询(优化)
这一点非常重要,必须单独拎出来说,否则你会误解 Next-Key Lock 的使用范围。
如果执行的是 命中主键或唯一索引 的等值查询:
SQL 示例:
加锁逻辑:
- MySQL 会对 Next-Key Lock 进行优化。
- 因为
id=20是唯一的,不可能再插入一条id=20的数据,所以不需要锁前面的间隙。 - Next-Key Lock 退化为 Record Lock。
- 锁范围:仅锁住
id=20这一行。
后果:其他事务完全可以插入 id=15 或 id=25,但修改 id=20 会被阻塞。
总结表
| SQL 类型 | 索引类型 | 命中情况 | 加锁类型 | 锁的范围示例 |
|---|---|---|---|---|
WHERE id = 20 | 唯一索引 | 命中 | Record Lock | 锁 20 这一行 |
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 UPDATE、UPDATE 或 DELETE,且你的 SQL 是 范围查询 或者 命中了非唯一索引的等值查询,MySQL 就会使用 Next-Key Lock 来锁住索引记录及其前方的间隙,从而彻底解决幻读问题。