Mysql--基础知识点--98--临键锁 VS 间隙锁

在 MySQL InnoDB 中,临键锁间隙锁 都是用于解决幻读(在 RR 隔离级别下)的锁机制,但它们的作用范围不同:

  • 间隙锁(Gap Lock) :只锁定索引记录之间的"间隙",不锁定记录本身
  • 临键锁(Next-Key Lock) :是行锁 + 间隙锁 的组合,锁定一个记录及其前面的间隙(左开右闭区间)。

主要区别

维度 间隙锁 临键锁
锁定范围 仅间隙(记录之间的空间) 记录本身 + 该记录前面的间隙
是否锁定记录 ❌ 否 ✅ 是
典型产生场景 查询条件使用范围但命中不存在的记录 默认的索引扫描/范围查询(RR隔离级别)
目的 防止幻读(阻止其他事务在间隙中插入新记录) 防止幻读 + 保护已有记录

举例说明

假设有一个表 tid 是主键,现有记录:1, 3, 5

(当前事务隔离级别为 REPEATABLE READ

1. 临键锁示例

sql 复制代码
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id = 3 FOR UPDATE;

InnoDB 会对 id=3 这条记录加上临键锁 ,锁定的区间为 (1, 3] (即从上一个记录 1 到当前记录 3 的区间,包含 3 本身)。

此时:

  • 事务 B 尝试 INSERT INTO t VALUES (2);阻塞 (因为 (1,3) 间隙被锁)
  • 事务 B 尝试 INSERT INTO t VALUES (3);阻塞 (记录 3 被锁)
  • 事务 B 尝试 INSERT INTO t VALUES (4);不阻塞4 属于间隙 (3,5),未被锁)

2. 间隙锁示例

sql 复制代码
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id = 2 FOR UPDATE;  -- id=2 不存在

因为 id=2 不存在,InnoDB 只对间隙 (1, 3) 加上间隙锁不会锁定任何记录 (记录 13 本身未被锁)。

此时:

  • 事务 B 尝试 INSERT INTO t VALUES (2);阻塞 (在间隙 (1,3) 中插入)
  • 事务 B 尝试 INSERT INTO t VALUES (1);不阻塞1 是已存在记录,且未被锁;但若 1 不存在,插入 1 也不阻塞,因为间隙锁范围是 (1,3),不包括 1
  • 事务 B 尝试 INSERT INTO t VALUES (3);不阻塞 (记录 3 本身没有被锁)
  • 事务 B 尝试 UPDATE t SET id = 3 WHERE id = 1; → 可以执行(不影响间隙)

小结

  • 临键锁 = 行锁 + 间隙锁,锁定范围更广,用于保护现有记录及防止相邻间隙的幻读。
  • 间隙锁 仅锁住"空白区域",不影响已有记录的修改或删除,只阻止在间隙中插入新数据。

在实际使用中,InnoDB 默认采用临键锁;

  • 唯一索引
    • 等值查询:当命中唯一索引等值查询且记录存在时,临键锁会退化为行锁;当查询不存在的记录时,则会退化为间隙锁。
    • 范围查询: 看下边的问题范围查询时用的是行锁、临键锁、行锁 哪个? 的答案
  • 非唯一索引
    • 等值查询: 当命中非唯一索引等值查询且记录存在时,还是间隙锁不会退化,原因看下边的问题 当命中非唯一索引等值查询且记录存在时会加什么样的锁;当查询不存在的记录时,则会退化为间隙锁。
    • 范围查询:跟唯一索引差不多

问:范围查询时用的是行锁、临键锁、行锁 哪个?

在 MySQL InnoDB 的 RR(可重复读) 隔离级别下,范围查询 默认使用的是 临键锁(Next-Key Lock)

它本质上是 "行锁 + 间隙锁" 的组合,既锁定查询范围内的已有记录(行锁),也锁定这些记录之间的间隙(间隙锁),从而防止幻读。

不同情况的具体表现

场景 RR 隔离级别 RC 隔离级别
范围查询(如 WHERE id > 10 临键锁(锁定索引区间及间隙) 行锁(仅锁定命中的记录,无间隙锁)
唯一索引等值查询(记录存在) 行锁(临键锁退化为行锁) 行锁
唯一索引等值查询(记录不存在) 间隙锁 无锁(或行锁?RC 下不加间隙锁,不会阻塞插入)

举例说明(RR 级别)

t(id PK) 有记录:1, 3, 5, 7

sql 复制代码
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id > 3 FOR UPDATE;   -- 范围查询

此时 InnoDB 会加临键锁,锁定的索引范围是:

  • id=3 的下一条记录 5 开始,直到正无穷。
  • 具体会锁住 (3,5](5,7](7, +∞) 这些区间(包含记录本身和前面的间隙)。

影响

  • 其他事务无法在 id > 3 的范围内插入新记录 (如插入 468 都会阻塞),也无法修改或删除 57 等已有记录。
  • 这保证了在事务 A 结束前,反复执行相同的范围查询,结果集始终不变(防止幻读)。

与 RC 的对比

如果隔离级别是 RC(读已提交)

  • 范围查询只加行锁 (仅锁住实际查到的记录 57)。
  • 不会锁间隙 ,因此其他事务可以在间隙中插入新记录(如插入 4),导致幻读。

总结

  • RR 隔离级别 + 范围查询 → 使用 临键锁(行锁+间隙锁)
  • RC 隔离级别 + 范围查询 → 只使用 行锁(无间隙锁,无临键锁)

所以,回答你的问题:范围查询在 InnoDB 中(RR 下)用的是 临键锁


再问:在RR级别,那如果是 表 t(id PK) 有记录:1, 3, 5, 7。id>=3 呢,锁住的范围是什么?

RR(可重复读) 隔离级别下,对 id >= 3 的范围查询使用 FOR UPDATE 加锁时,InnoDB 会使用**临键锁(Next-Key Lock)**锁定以下范围:

锁定的具体范围

表中有记录:1, 3, 5, 7
WHERE id >= 3 会命中记录 3, 5, 7

1. 对每个命中记录加临键锁(左开右闭区间)

  • 对于 id = 3:临键锁区间为 (1, 3]
  • 对于 id = 5:临键锁区间为 (3, 5]
  • 对于 id = 7:临键锁区间为 (5, 7]

2. 还需要锁定最大记录之后的间隙

  • 最后一个记录 7 之后的正无穷区间:(7, +∞)(也是一个间隙锁)

3. 综合结果

锁定的所有区间为:
(1, 3](3, 5](5, 7](7, +∞) = (1, +∞)

即:所有大于 1 的 id 值(包括记录和间隙)都被锁住

  • 记录被锁id = 3, 5, 7 不能由其他事务修改或删除。
  • 间隙被锁 :任何在 (1, +∞) 范围内的插入操作 (如插入 2, 4, 6, 8, 100)都会被阻塞。
  • 记录 1 不受影响 :其他事务可以正常修改或删除 id = 1 的记录。

示例验证

其他事务的操作 是否阻塞 原因
INSERT INTO t VALUES (2); ✅ 阻塞 落在间隙 (1,3)
INSERT INTO t VALUES (4); ✅ 阻塞 落在间隙 (3,5)
INSERT INTO t VALUES (6); ✅ 阻塞 落在间隙 (5,7)
INSERT INTO t VALUES (8); ✅ 阻塞 落在间隙 (7, +∞)
UPDATE t SET id=... WHERE id=3; ✅ 阻塞 记录 3 被临键锁锁定
DELETE FROM t WHERE id=5; ✅ 阻塞 记录 5 被临键锁锁定
UPDATE t SET ... WHERE id=1; ❌ 不阻塞 记录 1 未被锁
INSERT INTO t VALUES (0); ❌ 不阻塞 落在 (-∞,1] 区间,未锁定

总结

  • id >= 3 锁定的范围是 id > 1 的所有空间(即 (1, +∞))。
  • 相当于从第一个命中记录的前一个间隙开始(不包括前一个记录本身),一直锁到正无穷。
  • 这是 InnoDB 在 RR 级别下为了防止幻读所采取的标准行为。

再问:当命中非唯一索引等值查询且记录存在时会加什么样的锁

RR(可重复读) 隔离级别下,当命中非唯一索引等值查询且记录存在 时(例如 SELECT ... WHERE name = 'Alice' FOR UPDATEname 为非唯一索引),InnoDB 会加 临键锁(Next-Key Lock) ,而不会退化为行锁。

加锁的具体规则

1. 对辅助索引加临键锁

  • 对于找到的每条满足条件的辅助索引记录,InnoDB 会对其加上临键锁,锁定区间为 (上一个不同索引值, 当前索引值]
  • 由于非唯一索引可能存在多条相同值 的记录,InnoDB 会锁定所有相同值记录及其之间的间隙 ,并且还会锁定从最后一个相同值到下一个不同值之间的间隙(即右边界扩展到下一个不同值)。
  • 最终效果:锁住从上一个不同值到下一个不同值之间的整个区间(不包含左边界和右边界记录本身,但包含区间内的所有记录和间隙)。

2. 对聚集索引(主键)加行锁

  • 对于每个匹配的辅助索引记录对应的主键记录 ,InnoDB 也会加上行锁(Record Lock),防止其他事务通过主键直接修改或删除这些行。

举例说明

假设有一个表 t

sql 复制代码
CREATE TABLE t (
    id INT PRIMARY KEY,
    name VARCHAR(10),
    INDEX idx_name (name)
);
INSERT INTO t VALUES (1, 'Alice'), (2, 'Alice'), (3, 'Bob'), (4, 'Charlie');

RR 隔离级别下,事务 A 执行:

sql 复制代码
BEGIN;
SELECT * FROM t WHERE name = 'Alice' FOR UPDATE;

加锁情况

  • 辅助索引 idx_name 上的值分布:'Alice' (id=1), 'Alice' (id=2), 'Bob' (id=3), 'Charlie' (id=4)
  • 上一个不同索引值:无(可以视为 -∞
  • 当前索引值:'Alice'
  • 下一个不同索引值:'Bob'
  • InnoDB 会锁定 (-∞, 'Alice']('Alice', 'Bob'] 两个临键锁区间?实际上,由于 'Alice' 有多条,它会锁定从 -∞'Bob' 之间的所有间隙和记录,具体是:
    • 临键锁1:(-∞, 'Alice'](包含第一个 'Alice' 及其前面的间隙)
    • 临键锁2:('Alice', 'Alice'] ?不,对于第二条 'Alice',其前一条是第一条 'Alice',所以临键锁是 ('Alice', 'Alice'],这个区间实际上只包含第二条 'Alice' 本身(因为两条相同的值之间没有间隙)。
    • 然后还会对 'Alice''Bob' 之间的间隙加锁:即临键锁 ('Alice', 'Bob'](包含 'Bob' 吗?不,临键锁是左开右闭,'Bob' 本身属于这个锁,但 'Bob' 是下一个不同值,是否会被锁?实际上,这个临键锁会锁住 'Bob' 记录本身,但 'Bob' 不满足 name='Alice',所以这个锁是必要的,以防止幻读?重要 :根据 InnoDB 实现,范围查询会扫描到第一个不满足条件的记录,并对该记录也加临键锁。因此,对于等值查询 name='Alice',InnoDB 会定位到第一条 'Alice',然后继续扫描直到第一个不等于 'Alice' 的记录(即 'Bob'),并对 'Bob' 也加上临键锁 ('Alice', 'Bob']。这样,其他事务无法在 ('Alice', 'Bob') 间隙中插入新记录(如插入 'Alice''Alice2'),也无法修改或删除 'Bob' 本身?实际上,'Bob' 被锁住后,其他事务不能修改或删除 id=3 这一行,但这是否合理?这其实是 InnoDB 为了防止幻读的保守策略:因为如果允许修改 'Bob''Alice',就会导致新 'Alice' 出现,影响当前查询结果。所以确实会锁住 'Bob'

因此,最终的锁定范围是 -∞'Bob' 的所有间隙和记录 (包含所有 'Alice''Bob' 记录)。这样,其他事务:

  • 无法插入 name='Alice' 的新记录(因为间隙被锁)
  • 无法插入 name 值在 'Alice''Bob' 之间的记录(如 'Alice2',按字符串排序在 'Alice''Bob' 之间)
  • 无法修改 'Bob' 为其他值,也无法修改 'Alice' 记录
  • 无法删除 'Alice''Bob' 记录

对比唯一索引等值查询(记录存在):

  • 唯一索引不需要锁定下一个不同值,因为唯一性保证了不会有新记录插入相同值,且修改其他值不影响等值查询。所以退化为行锁。

总结

索引类型 等值查询记录存在 RR级别下加的锁
唯一索引(含主键) 行锁(退化为 Record Lock)
非唯一索引 临键锁(Next-Key Lock,锁定一个区间,包含间隙和记录)

因此,回答你的问题:非唯一索引等值查询且记录存在时,加的是临键锁(并且还会对对应的主键记录加行锁)。

相关推荐
HUGu RGIN1 天前
MySQL--》如何在MySQL中打造高效优化索引
android·mysql·adb
HackTwoHub1 天前
AI大模型网关存在SQL注入、附 POC 复现、影响版本LiteLLM 1.81.16~1.83.7(CVE-2026-42208)
数据库·人工智能·sql·网络安全·系统安全·网络攻击模型·安全架构
l1t1 天前
DeepSeek总结的DuckLake构建基于 SQL 原生表格式的下一代数据湖仓
数据库·sql
KmSH8umpK1 天前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第八篇
数据库·redis·分布式
TDengine (老段)1 天前
从施工监测到运营预警,桥科院用 TDengine 提升桥梁数据管理能力
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
S1998_1997111609•X1 天前
论mysql国盾shell-sfa犯罪行为集团下的分项工程及反向注入原理尐深度纳米算法下的鐌檵鄐鉎行为
网络·数据库·网络协议·百度·开闭原则
KmSH8umpK1 天前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第七篇
数据库·redis·分布式
BU摆烂会噶1 天前
【LangGraph】持久化实现的三大能力——时间旅行
数据库·人工智能·python·postgresql·langchain
l1t1 天前
DeepSeek总结的DuckLake 入门
数据库
Joseph Cooper1 天前
RAG 与 AI Agent:智能体真的需要检索增强生成吗?
数据库·人工智能·ai·agent·rag·上下文工程