什么是间隙(Gap)
假设表中存在索引值为1, 3, 5
的记录,那么可能的间隙包括:(-∞, 1)
(1, 3)
(3, 5)
(5, +∞)
间隙锁会锁定这些区间,阻止其他事务在区间内插入新数据。
间隙锁的作用
解决幻读问题:防止其他事务插入符合当前事务查询条件的新数据。
保证范围查询的一致性:例如
SELECT ... WHERE id BETWEEN 10 AND 20
,间隙锁会锁定10
到20
之间的所有间隙,即使某些值当前不存在。
与临键锁(Next-Key Lock)的关系
临键锁 = 行锁(Record Lock) + 间隙锁(Gap Lock),例如锁定区间
(3, 5]
(包含现有记录5
和其之前的间隙)。InnoDB 默认使用临键锁来实现可重复读隔离级别。
触发场景
范围查询
SELECT * FROM table WHERE id > 10 AND id < 20 FOR UPDATE;
会锁定
(10, 20)
之间的所有间隙,阻止插入11
到19
的新数据。唯一索引的非唯一查询
即使使用唯一索引,若查询条件是非唯一值(如WHERE col = 100
,但col
允许重复),也可能触发间隙锁。未命中记录的查询
SELECT * FROM table WHERE id = 15 FOR UPDATE;
如果表中没有
id=15
的记录,会锁定(10, 20)
的间隙(假设相邻记录为10
和20
)。
间隙锁的特性
仅针对可重复读隔离级别
在 读已提交(Read Committed) 级别下,InnoDB 会禁用间隙锁。基于索引
间隙锁依赖索引,如果查询未使用索引,InnoDB 会退化为锁全表(表锁或所有间隙)。共享锁与排他锁
共享间隙锁(S Gap Lock):允许其他事务读间隙,但禁止插入。
排他间隙锁(X Gap Lock):禁止其他事务读或插入。
示例与影响
场景:事务 A 和事务 B 的冲突
事务 A 执行:
SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE;
锁定
(20, 30)
之间的间隙。事务 B 尝试插入:
INSERT INTO users (age) VALUES (25); -- 被阻塞
事务 B 必须等待事务 A 释放间隙锁。
注意事项
死锁风险
间隙锁可能导致死锁。例如:事务 A 锁定
(10, 20)
,尝试插入15
。事务 B 锁定
(5, 15)
,尝试插入12
。
两者互相等待对方释放间隙锁,导致死锁。
性能影响
过大的间隙锁范围会降低并发性能,需合理设计索引和查询条件。禁用间隙锁
可通过将隔离级别降为 读已提交,或设置innodb_locks_unsafe_for_binlog=1
(不推荐,破坏一致性)