之前的文章《MySQL 里的几把锁》提到了临键锁,今天来系统学习下:
Next-Key Lock(临键锁)是 MySQL InnoDB 引擎在 可重复读(RR) 和 串行化(SERIALIZABLE) 隔离级别下实现的核心锁机制,本质是「行锁(Record Lock) + 间隙锁(Gap Lock)」的组合,核心目标是阻止幻读,同时保证数据的一致性。
一、Next-Key Lock 的核心定义
- 本质:覆盖「索引记录本身 + 该记录与下一条记录之间的间隙」的锁;
- 作用范围 :针对范围查询 (如
WHERE age > 20、WHERE id BETWEEN 1 AND 10)生效,精准等值查询(命中唯一索引)会降级为行锁; - 生效级别:仅在 RR / 串行化级别生效(RC 级别下自动禁用间隙锁,仅保留行锁);
- 核心目标:阻止其他事务在锁定的间隙中插入新数据,从根源上解决幻读问题。
二、Next-Key Lock 的组成部分
Next-Key Lock 由两个子锁组成,需先理解这两个基础锁:
| 锁类型 | 作用对象 | 核心行为 | 示例(表 user,id 为主键,数据:1、3、5) |
|---|---|---|---|
| 行锁(Record Lock) | 已存在的索引记录 | 锁住具体的行数据,阻止其他事务修改 / 删除该行 | 锁住 id=3 的行,其他事务无法更新 / 删除 id=3 |
| 间隙锁(Gap Lock) | 索引记录之间的 "间隙" | 锁住行与行之间的空白区域,阻止其他事务插入新行(无数据的位置也会被锁) | 锁住 (3,5) 间隙,其他事务无法插入 id=4 |
间隙的划分规则
InnoDB 按索引顺序划分间隙,包含「最小值左侧间隙」「行之间间隙」「最大值右侧间隙」:以上述 id 主键为例,间隙范围为:(-∞,1)、(1,3)、(3,5)、(5,+∞)
三、Next-Key Lock 的锁定范围(核心)
Next-Key Lock 的锁定范围遵循「左闭右开」原则:锁定当前索引记录(闭区间) + 下一条记录前的间隙(开区间)。
示例 1:主键索引的范围查询
假设表 user 主键 id 数据:1、3、5,事务 A 执行:
sql
START TRANSACTION;
-- 范围查询,加 Next-Key Lock
SELECT * FROM user WHERE id > 3 FOR UPDATE;
此时事务 A 的 Next-Key Lock 锁定范围:
- 行锁:锁住 id=5 的行;
- 间隙锁:锁住 (3,5)、(5,+∞) 间隙;→ 整体锁定范围:
(3, +∞)(左开右开?实际是[5, +∞)行锁 +(3,5)间隙锁,即 Next-Key Lock 范围为(3,5] + (5,+∞))。
效果:其他事务无法:
- 修改 / 删除 id=5 的行(行锁);
- 插入 id=4、id=6、id=10 等(间隙锁阻止)。
示例 2:等值查询的锁降级
若事务 A 执行精准等值查询(命中唯一主键):
sql
SELECT * FROM user WHERE id = 3 FOR UPDATE;
此时 Next-Key Lock 会降级为行锁,仅锁住 id=3 的行,不会锁间隙 → 其他事务可插入 id=2、id=4 等(因为无间隙锁)。
示例 3:等值查询未命中记录
若事务 A 查询不存在的记录:
sql
SELECT * FROM user WHERE id = 4 FOR UPDATE;
此时不会触发行锁,但会加间隙锁,锁定 (3,5) 间隙 → 其他事务无法插入 id=4(阻止幻读)。
四、Next-Key Lock 的典型场景与行为
场景 1:范围查询(核心场景)
sql
-- 事务A(RR级别)
START TRANSACTION;
SELECT * FROM user WHERE age > 20 FOR UPDATE; -- age 为普通索引,数据:20、25、30
-- 事务B
INSERT INTO user (age) VALUES (21); -- 阻塞(间隙锁锁住 (20,25) 间隙)
原因 :事务 A 的 Next-Key Lock 锁定 (20,25](行锁 25 + 间隙锁 (20,25))、(25,30]、(30,+∞),事务 B 插入的 21 落在 (20,25) 间隙,被阻塞。
场景 2:唯一索引 vs 非唯一索引
| 查询类型 | 锁类型 | 示例(id 主键,age 普通索引) |
|---|---|---|
| 唯一索引等值查询(命中) | 行锁(降级) | WHERE id=3 → 仅锁 id=3 行 |
| 唯一索引等值查询(未命中) | 间隙锁 | WHERE id=4 → 锁 (3,5) 间隙 |
| 非唯一索引等值查询 | Next-Key Lock | WHERE age=25 → 锁 (20,25] + (25,30) 间隙 |
场景 3:RC 级别下的行为
RC 级别下 InnoDB 自动禁用间隙锁,仅保留行锁:
sql
-- 事务A(RC级别)
START TRANSACTION;
SELECT * FROM user WHERE age > 20 FOR UPDATE;
-- 事务B
INSERT INTO user (age) VALUES (21); -- 成功插入(无间隙锁)→ 出现幻读
这也是 RC 级别无法解决幻读的核心原因。
五、Next-Key Lock 的优缺点
优点
- 彻底解决 RR 级别下的幻读问题(阻止间隙插入);
- 兼顾并发与一致性(比串行化级别性能高);
- 锁粒度可控(等值查询命中唯一索引时降级为行锁,减少锁竞争)。
缺点
- 锁范围扩大可能导致锁等待 / 死锁:如多个事务锁定重叠的间隙,容易触发死锁;
- 影响插入性能:间隙锁会阻止合法的插入操作(如上述 id=4 的插入);
- 仅适用于 RR / 串行化级别:RC 级别下失效,无法解决幻读。
六、死锁示例(Next-Key Lock 导致)
sql
-- 事务A
START TRANSACTION;
SELECT * FROM user WHERE id > 2 FOR UPDATE; -- 锁定 (2,+∞) 间隙(包含 3、5)
-- 事务B
START TRANSACTION;
SELECT * FROM user WHERE id < 4 FOR UPDATE; -- 锁定 (-∞,4) 间隙(包含 1、3)
-- 事务A尝试插入 id=4
INSERT INTO user (id) VALUES (4); -- 等待事务B释放 (3,5) 间隙锁
-- 事务B尝试插入 id=4
INSERT INTO user (id) VALUES (4); -- 等待事务A释放 (2,+∞) 间隙锁 → 死锁
解决思路:尽量使用精准等值查询(命中唯一索引),减少范围查询;控制事务执行时长,及时提交。
七、关键规则总结
- 锁定范围:左闭右开(当前记录闭区间 + 下一条记录前的间隙开区间);
- 降级规则:唯一索引的等值查询(命中)→ 行锁;未命中 → 间隙锁;
- 生效级别:仅 RR / 串行化级别,RC 级别禁用间隙锁;
- 核心目标:阻止间隙插入,解决幻读;
- 副作用:可能导致锁等待 / 死锁,需合理设计查询语句。
总结
Next-Key Lock 是 InnoDB 解决 RR 级别幻读的核心手段,通过「行锁 + 间隙锁」覆盖索引记录和间隙,阻止其他事务插入新数据。使用时需注意:
- 范围查询会扩大锁范围,尽量使用精准等值查询(命中唯一索引);
- RC 级别下无间隙锁,需接受幻读;
- 避免长时间持有锁,减少死锁风险。