间隙锁(Gap Lock)
间隙锁(Gap Lock)是数据库在可重复读(Repeatable Read)隔离级别下(以InnoDB引擎为例),为防止幻读而引入的一种行级锁机制(不需要开发人员主动 “显式添加”)。它锁定的是索引记录之间的“间隙”,而非具体的索引记录本身,目的是阻止其他事务在该间隙中插入新的记录,从而保证事务读取数据的一致性。
一、间隙的定义
“间隙”指的是索引记录之间的空白区域,包括:
- 两条相邻索引记录之间的区间(如索引值10和20之间的间隙);
- 第一条索引记录之前的区间(如小于最小索引值5的间隙);
- 最后一条索引记录之后的区间(如大于最大索引值30的间隙)。
例如,表中已有索引值为 10、20、30
的记录,那么间隙包括:
(-∞, 10)
(10, 20)
(20, 30)
(30, +∞)
二、间隙锁的触发场景
当事务对某条记录加锁时(如SELECT ... FOR UPDATE
或UPDATE
),若使用范围条件或等值条件但未命中记录,InnoDB会对符合条件的索引间隙加锁。
示例1:范围查询加锁
假设有表 user(id, age)
,age
是索引列,数据如下:
id | age |
---|---|
1 | 10 |
3 | 20 |
5 | 30 |
事务A执行:
-- 范围查询,锁定age在15~25之间的记录及间隙
SELECT * FROM user WHERE age BETWEEN 15 AND 25 FOR UPDATE;
此时,事务A会锁定 age
在 (10, 30)
之间的间隙(因为15~25落在10和30之间)。
其他事务若尝试插入 age=18
或 age=22
的记录,会被阻塞,直到事务A提交释放锁。
示例2:等值查询未命中记录
事务A执行:
-- 等值查询,但表中没有age=25的记录
SELECT * FROM user WHERE age = 25 FOR UPDATE;
此时,InnoDB会锁定 age=25
可能插入的间隙(即(20, 30)
),其他事务插入 age=25
会被阻塞。
三、间隙锁的特点
- 只锁间隙,不锁记录:间隙锁不会阻止对已有记录的修改,仅阻止新记录插入。
- 与隔离级别相关:仅在 可重复读(RR) 级别生效,读已提交(RC)及以下级别不会使用间隙锁(因此RC级别可能出现幻读)。
- 可能导致死锁:多个事务对重叠的间隙加锁时,可能因等待对方释放锁而产生死锁。
- 例如:事务A锁定
(10, 20)
,事务B锁定(15, 25)
,两者都想插入age=18
的记录,会相互等待对方释放锁。
- 例如:事务A锁定
四、间隙锁与其他锁的关系
- 记录锁(Record Lock):锁定具体的索引记录(如
age=20
的记录)。 - 临键锁(Next-Key Lock):InnoDB的默认行锁方式,是“记录锁+间隙锁”的组合,锁定索引记录及其之前的间隙(如锁定
age=20
的记录,同时锁定(10, 20)
的间隙)。
五、间隙锁的核心作用
解决幻读问题。幻读是指:一个事务在多次查询中,由于其他事务插入了新数据,导致前后查询结果不一致(多出原本不存在的记录)。
间隙锁是临键锁的一部分,临键锁锁定的范围是一个左开右闭区间,例如 (10, 20]
,其中:
- 记录锁:锁定具体的索引记录(如
id=20
),阻止其他事务修改或删除该记录。 - 间隙锁:锁定索引记录之间的空白区域(如
(10, 20)
),阻止其他事务在该区间插入新记录。
采用临键锁确保了在事务执行期间,查询范围内的所有可能插入新数据的位置都被锁定,从而避免幻读。