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 级别下无间隙锁,需接受幻读;
  • 避免长时间持有锁,减少死锁风险。
相关推荐
luoluoal10 小时前
基于python的医疗问句中的实体识别算法的研究(源码+文档)
python·mysql·django·毕业设计·源码
数据知道10 小时前
PostgreSQL 性能优化:连接数过多的原因分析与连接池方案
数据库·postgresql·性能优化
怣5010 小时前
MySQL子查询实战指南:数据操作(增删改查)与通用表达式
数据库·chrome·mysql
范纹杉想快点毕业10 小时前
从单片机基础到程序框架:构建嵌入式系统的完整路径
数据库·mongodb
数据知道10 小时前
PostgreSQL性能优化:如何定期清理无用索引以释放磁盘空间(索引膨胀监控)
数据库·postgresql·性能优化
喵叔哟10 小时前
67.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--分摊功能总体设计与业务流程
数据库·微服务·架构
tryCbest10 小时前
Oracle查看存储过程
数据库·oracle
咩咩不吃草10 小时前
【MySQL】表和列、增删改查语句及数据类型约束详解
数据库·mysql·语法
不懒不懒10 小时前
【MySQL 实战:从零搭建规范用户表(含完整 SQL 与避坑指南)】
数据库
ID_1800790547310 小时前
Python结合淘宝关键词API进行商品价格监控与预警
服务器·数据库·python