MySQL 临键锁

之前的文章《MySQL 里的几把锁》提到了临键锁,今天来系统学习下:

Next-Key Lock(临键锁)是 MySQL InnoDB 引擎在 可重复读(RR)串行化(SERIALIZABLE) 隔离级别下实现的核心锁机制,本质是「行锁(Record Lock) + 间隙锁(Gap Lock)」的组合,核心目标是阻止幻读,同时保证数据的一致性。

一、Next-Key Lock 的核心定义

  • 本质:覆盖「索引记录本身 + 该记录与下一条记录之间的间隙」的锁;
  • 作用范围 :针对范围查询 (如 WHERE age > 20WHERE 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 锁定范围:

  1. 行锁:锁住 id=5 的行;
  2. 间隙锁:锁住 (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 的优缺点

优点
  1. 彻底解决 RR 级别下的幻读问题(阻止间隙插入);
  2. 兼顾并发与一致性(比串行化级别性能高);
  3. 锁粒度可控(等值查询命中唯一索引时降级为行锁,减少锁竞争)。
缺点
  1. 锁范围扩大可能导致锁等待 / 死锁:如多个事务锁定重叠的间隙,容易触发死锁;
  2. 影响插入性能:间隙锁会阻止合法的插入操作(如上述 id=4 的插入);
  3. 仅适用于 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,+∞) 间隙锁 → 死锁

解决思路:尽量使用精准等值查询(命中唯一索引),减少范围查询;控制事务执行时长,及时提交。

七、关键规则总结

  1. 锁定范围:左闭右开(当前记录闭区间 + 下一条记录前的间隙开区间);
  2. 降级规则:唯一索引的等值查询(命中)→ 行锁;未命中 → 间隙锁;
  3. 生效级别:仅 RR / 串行化级别,RC 级别禁用间隙锁;
  4. 核心目标:阻止间隙插入,解决幻读;
  5. 副作用:可能导致锁等待 / 死锁,需合理设计查询语句。

总结

Next-Key Lock 是 InnoDB 解决 RR 级别幻读的核心手段,通过「行锁 + 间隙锁」覆盖索引记录和间隙,阻止其他事务插入新数据。使用时需注意:

  • 范围查询会扩大锁范围,尽量使用精准等值查询(命中唯一索引);
  • RC 级别下无间隙锁,需接受幻读;
  • 避免长时间持有锁,减少死锁风险。
相关推荐
118路司机1 小时前
ClickHouse常用DDL
数据库·clickhouse
哈哈老师啊1 小时前
Springboot学生选课系统576i3(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
北友舰长1 小时前
基于Springboot+vue大型商场应急预案管理系统的设计与实现【Java毕业设计·安装调试·代码讲解·文档报告】
java·vue.js·spring boot·mysql·商场·应急处理·应急
不会写程序的未来程序员1 小时前
Redis 哨兵(Sentinel)原理
数据库·redis·sentinel
Lisonseekpan1 小时前
技术选型分析:MySQL、Redis、MongoDB、ElasticSearch与大数据怎么选?
大数据·redis·后端·mysql·mongodb·elasticsearch
梁萌1 小时前
MySQL关联查询原理与优化
数据库·mysql
⑩-1 小时前
Spring 的事务传播行为(Propagation)
java·数据库·spring
綝~1 小时前
Excel导入MongoDB操作手册
数据库·excel
哈库纳玛塔塔1 小时前
MongoDB 数据库 ORM/ODM 新工具
java·数据库·spring boot·mongodb·orm