Next-Key Lock和间隙锁区别
Next-Key Lock 和间隙锁(Gap Lock)是 InnoDB 中两种不同的锁机制,它们的设计目标和使用场景有所区别,但都与 防止幻读(Phantom Read) 和 保证范围查询的一致性 密切相关。以下是它们的核心区别和具体应用场景:
- 定义与作用范围
锁类型 | 定义 | 锁定范围 | 示例(索引值为 10, 20, 30) |
---|---|---|---|
Next-Key Lock | 记录锁(Record Lock) + 间隙锁(Gap Lock),锁定 左开右闭区间 的索引记录和间隙。 | 锁定一个记录及其之前的间隙 | (10, 20](锁定间隙 10~20 和记录 20) |
间隙锁(Gap Lock) | 仅锁定 索引记录之间的间隙,不锁定记录本身。 | 锁定两个索引值之间的间隙 | (10, 20)(仅锁定间隙,不锁定 10 或 20) |
- 核心区别
特性 | Next-Key Lock | 间隙锁(Gap Lock) |
---|---|---|
锁定对象 | 索引记录 + 之前的间隙 | 仅索引记录之间的间隙 |
是否锁定记录 | 是(记录锁部分) | 否 |
解决的核心问题 | 防止幻读和不可重复读 | 仅防止幻读(禁止在间隙插入新数据) |
触发场景 | 范围查询、非唯一索引的精确查询 | 范围查询未命中记录、唯一索引未命中时 |
锁冲突 | 与插入、更新、删除操作均可能冲突 | 仅与插入操作冲突 |
兼容性 | 与其他事务的间隙锁和 Next-Key Lock 不兼容 | 允许其他事务对同一间隙加间隙锁 |
- Next-Key Lock 的典型场景
范围查询:
SELECT * FROM t WHERE id > 10 AND id < 20 FOR UPDATE;
锁定区间 (10, 20],阻止其他事务插入 id=15 或修改 id=20 的记录。
非唯一索引的精确查询:
SELECT * FROM t WHERE name = 'Alice' FOR UPDATE; -- name 是普通索引
锁定 name='Alice' 的索引记录及其之前的间隙(如 (prev, 'Alice']),防止其他事务插入新的 name='Alice' 记录。
- 间隙锁(Gap Lock)的典型场景
范围查询未命中记录:
SELECT * FROM t WHERE id = 25 FOR UPDATE; -- id=25 不存在
锁定 (20, 30) 的间隙,阻止插入 id=25。
唯一索引未命中时:
SELECT * FROM t WHERE id = 25 FOR UPDATE; -- id 是唯一索引且不存在
锁定 (20, 30) 的间隙,但不锁定任何记录(唯一索引未命中时退化为间隙锁)。
锁的行为示例
场景 1:Next-Key Lock 的锁定范围
-- 表 t 的索引值: 10, 20, 30
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id = 20 FOR UPDATE; -- 锁定 (10, 20]
-- 事务 B
INSERT INTO t (id) VALUES (15); -- 阻塞!因为 15 在 (10, 20] 的锁定范围内
UPDATE t SET id = 25 WHERE id = 20; -- 阻塞!记录锁部分冲突
场景 2:间隙锁的锁定范围
-- 表 t 的索引值: 10, 20, 30
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id = 25 FOR UPDATE; -- 锁定 (20, 30) 的间隙
-- 事务 B
INSERT INTO t (id) VALUES (25); -- 阻塞!因为 25 在 (20, 30) 的间隙内
UPDATE t SET id = 25 WHERE id = 20; -- 允许!间隙锁不锁定记录本身
- 锁的优化建议
优先使用唯一索引:
唯一索引的精确查询会退化为记录锁(如 WHERE id = 20),减少锁范围。
避免全表扫描:
无索引的查询可能导致全表 Next-Key Lock,严重影响并发。
缩小查询范围:
尽量使用精确条件(如 WHERE id IN (1,2,3))而非大范围查询(如 WHERE id > 1000)。
控制事务粒度:
及时提交事务,减少锁的持有时间。