间隙锁中「间隙」的核心含义:通俗拆解+实例+核心规则

间隙锁(Gap Lock)是InnoDB在可重复读(RR) 隔离级别下,为解决当前读幻读设计的锁机制,而「间隙」是间隙锁的锁定对象,核心结论先明确:间隙不是物理数据行之间的物理空隙,而是InnoDB根据索引排序后索引记录之间的逻辑空区间,也包括索引首条记录前、最后一条记录后的边界空区间**。

简单说:InnoDB会把表中的索引值按大小排序,相邻两个索引值之间的“空位置”、以及首尾索引值外侧的“空位置”,就是「间隙」;间隙锁的作用,就是锁定这些空的逻辑区间,阻止其他事务在区间内插入新的索引记录(新行),从根源上杜绝幻影行的产生。

结合你之前了解的SELECT ... FOR UPDATE、行锁、临键锁,这里的「间隙」是基于索引存在的——没有索引,就没有间隙(无索引时会直接升级为表锁,不存在间隙锁),且不同索引(主键/普通索引)会生成各自的间隙,这是理解间隙的核心前提。

一、「间隙」的通俗定义+核心特征

1. 通俗理解

把表的某一列索引值升序排列,就像一条标了刻度的尺子:

  • 尺子上有刻度的地方 = 表中已存在的索引记录
  • 两个刻度之间的空白段、尺子最左侧刻度左边的空白、最右侧刻度右边的空白 = 间隙
  • 间隙锁就是“把尺子上的某段空白段锁起来,不让人在这段空白上刻新的刻度”。

2. 核心特征(必记,避免理解误区)

  1. 逻辑性:间隙是逻辑概念,与物理数据行的存储位置无关,只由索引值的排序结果决定;
  2. 索引依赖性必须基于索引,主键索引/普通索引会生成各自的间隙,无索引则无间隙;
  3. 区间性:间隙是左开右开的空区间(如(1,3)),区间内没有任何已存在的索引记录;
  4. 边界性:包含首尾边界间隙(最小编索引前的(-∞, 最小值)、最大索引后的(最大值, +∞));
  5. 无数据性:间隙内没有任何已存在的行数据,间隙锁锁定的是“插入新行的位置”,而非已存在的数据。

二、最易理解的实例:基于主键索引的间隙

主键索引是InnoDB的聚簇索引,每张表必有,且主键值唯一、有序,是理解间隙的最佳切入点,用实际数据举例子,一眼就能看懂。

示例表&数据(和之前一致,主键索引id)

CREATE TABLE `test_lock` (
  `id` INT PRIMARY KEY AUTO_INCREMENT, -- 主键索引,唯一、有序
  `name` VARCHAR(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入3条数据,主键id分别为1、3、5(故意留空2、4,方便看间隙)
INSERT INTO test_lock (id,name) VALUES (1,'a'),(3,'c'),(5,'e');

步骤1:对主键索引id按升序排序

得到已存在的索引值:1 → 3 → 5

步骤2:推导主键索引的所有间隙

根据「相邻索引间+首尾边界」的规则,生成4个左开右开的间隙

  1. 首边界间隙(-∞, 1) —— 小于最小主键值1的所有空区间;
  2. 中间间隙1(1, 3) —— 主键1和3之间的空区间(对应id=2的位置);
  3. 中间间隙2(3, 5) —— 主键3和5之间的空区间(对应id=4的位置);
  4. 尾边界间隙(5, +∞) —— 大于最大主键值5的所有空区间。

步骤3:间隙锁的作用

如果对某间隙加锁(比如(1,3)),其他事务无法在该间隙内插入新的主键记录(比如插入id=2的行),直到锁释放;这也是之前讲的「临键锁=行锁+间隙锁」的核心:锁定已存在的索引行(行锁),同时锁定该行两侧的间隙(间隙锁),阻止插入新行。

三、进阶实例:基于普通索引的间隙

普通索引(非唯一)也会生成自己的间隙,普通索引的间隙与主键索引无关,仅由普通索引自身的排序值决定,这是容易混淆的点,再举例子说明。

对示例表新增普通索引+补充数据

-- 给name加普通索引(非唯一)
ALTER TABLE test_lock ADD INDEX idx_name (name);
-- 再插入2条数据,此时name的普通索引值有:a、b、c、e、f
INSERT INTO test_lock (id,name) VALUES (2,'b'),(4,'f');

步骤1:对普通索引name按升序排序

得到已存在的索引值:a → b → c → e → f

步骤2:推导普通索引name的所有间隙

同样按「相邻索引间+首尾边界」规则,生成6个左开右开的间隙
(-∞,a)(a,b)(b,c)(c,e)(e,f)(f,+∞)

关键结论

如果执行SELECT * FROM test_lock WHERE name = 'c' FOR UPDATE(普通索引查询),InnoDB会对name='c'的行加行锁,同时对其两侧的间隙(b,c)(c,e)加间隙锁;此时其他事务无法在这两个间隙内插入name为新值的行(比如插入name='d'),但可以插入name='a1'(属于(-∞,a))、name='g'(属于(f,+∞))的行——因为这些间隙未被锁定。

四、特殊场景:空表/无相邻索引的间隙

1. 空表的间隙

如果表中没有任何数据,主键索引的间隙是唯一的(-∞, +∞);此时对空表执行SELECT * FROM test_lock FOR UPDATE,会锁定这个全局间隙,其他事务无法向表中插入任何数据(直到锁释放)。

2. 单条记录的间隙

如果表中只有1条数据(主键id=1),主键索引的间隙为:(-∞,1)(1,+∞);锁定id=1的行时,会同时锁定这两个间隙,阻止插入任何新行。

五、理解「间隙」的核心误区(避坑)

误区1:把「物理行间隙」当成「索引间隙」

错误认为:表中物理行之间的空行(比如id=1后没有id=2,物理存储上的空位置)是间隙;
正确认知:间隙是索引排序后的逻辑区间,哪怕物理行连续(id=1、2、3),主键索引的间隙依然是(-∞,1)(1,2)(2,3)(3,+∞),只是间隙内没有可插入的“连续id”,但间隙本身依然存在。

误区2:无索引也有间隙

错误认为:即使查询条件不用索引,也会有间隙锁;
正确认知:间隙锁仅基于索引存在,无索引/索引失效时,InnoDB会进行全表扫描,直接升级为表级排他锁,不再使用间隙锁(因为没有索引,无法生成逻辑间隙)。

误区3:主键间隙和普通索引间隙互通

错误认为:锁定主键索引的间隙,会同时锁定普通索引的间隙;
正确认知:不同索引的间隙相互独立,主键索引的间隙锁只影响主键的插入,普通索引的间隙锁只影响该普通索引的插入,彼此无关联。

六、「间隙」与间隙锁、临键锁的关联(衔接之前的知识)

理解了「间隙」,就能彻底搞懂RR隔离级别下的锁组合,核心关联就是:

  1. 行锁(Record Lock):锁定已存在的索引记录(间隙外的“刻度”),阻止其他事务修改/删除该记录;
  2. 间隙锁(Gap Lock):锁定索引的逻辑间隙(刻度间的空白),阻止其他事务插入新记录;
  3. 临键锁(Next-Key Lock)行锁+间隙锁的组合,是InnoDB在RR下的默认锁类型,锁定某一索引记录+该记录左侧的间隙(左闭右开区间),比如主键id=3的临键锁是(1,3],既锁id=3的行,又锁间隙(1,3)
  4. 三者的最终目的:协同解决RR隔离级别下当前读的幻读问题,行锁防修改,间隙锁防插入,临键锁则是二者的综合。

七、核心总结

  1. 间隙锁中的「间隙」是基于索引排序的逻辑空区间,非物理空隙,与索引值排序相关,与物理存储无关;
  2. 间隙的生成规则:相邻索引记录之间的区间 + 首条索引前的(-∞, 最小值) + 最后一条索引后的(最大值, +∞),均为左开右开
  3. 「间隙」是索引依赖的:主键/普通索引各有自己的间隙,无索引则无间隙,直接升级表锁;
  4. 间隙的作用:作为间隙锁的锁定对象,阻止其他事务在区间内插入新的索引记录,从根源上杜绝幻影行;
  5. 关键衔接:临键锁是行锁+间隙锁的组合,其锁定的“间隙”就是我们讲的索引逻辑区间,这也是SELECT ... FOR UPDATE在普通索引/范围查询下不会出现幻读的根本原因。
posted @ 2026-01-23 10:02  先弓  阅读(1)  评论(0)    收藏  举报