间隙锁本身不直接防止幻读,它只是next-key lock的一部分;真正起作用的是innodb在rr级别下对范围查询自动使用的next-key lock(record lock+gap lock组合),而快照读靠mvcc避免幻读,当前读才依赖间隙锁堵插入漏洞。

间隙锁本身不直接“防止幻读”,它只是Next-Key Lock的一部分;真正起作用的是InnoDB在RR级别下对范围查询自动使用的Next-Key Lock,即Record Lock + Gap Lock组合。如果你只做普通SELECT(快照读),根本不会触发间隙锁——幻读靠MVCC挡;只有SELECT ... FOR UPDATE、UPDATE、DELETE这类当前读,才真正依赖间隙锁来堵住插入漏洞。
Gap Lock只在RR级别生效,RC下完全不启用
很多人调低隔离级别后发现幻读重现,第一反应是“锁失效了”,其实是根本没加Gap Lock。在READ COMMITTED下:
-
WHERE age > 25 FOR UPDATE只会锁住实际存在的age=26、28、30这些行(Record Lock),(25,26)、(26,28)等间隙完全开放 - 其他事务随时能
INSERT INTO t (age) VALUES (27),导致你第二次SELECT ... FOR UPDATE多出一行 - 执行
SELECT @@transaction_isolation确认当前级别,别依赖客户端默认配置
Gap Lock不是独立加的,它永远依附于Next-Key Lock
你无法单独“加一个Gap Lock”,InnoDB只在需要时隐式构造Next-Key Lock。关键看查询是否走索引、是否为范围条件:
- 主键等值查询:
SELECT * FROM t WHERE id = 100 FOR UPDATE→ 只加Record Lock,无Gap部分 - 非唯一索引范围查询:
SELECT * FROM t WHERE name > 'Li' FOR UPDATE→ 加Next-Key Lock,包含name值落在('Li', next_existing_name)之间的所有间隙 - 全表扫描(没走索引):
SELECT * FROM t WHERE status = 'active' FOR UPDATE→ 锁住(-∞, min_key]到(max_key, +supremum]全部间隙,相当于逻辑锁表
Insert Intention Lock是Gap Lock的实际拦截点
新行插入不是直接撞上Gap Lock,而是先申请Insert Intention Lock——一种特殊的意向间隙锁。它和已有的Gap Lock/Next-Key Lock冲突,所以会被阻塞:
- 事务A执行
SELECT * FROM t WHERE val BETWEEN 10 AND 20 FOR UPDATE→ 加锁区间(5,12]和(12,25](假设有val=5、12、25) - 事务B执行
INSERT INTO t (val) VALUES (15)→ 尝试获取(12,25)上的Insert Intention Lock→ 与A持有的Gap Lock冲突 → 等待 - 如果B插的是val=30,而A没锁到那么远,就不会阻塞
- 注意:
INSERT ... ON DUPLICATE KEY UPDATE同样会触发这个检查,不是“避开锁”的捷径
最易被忽略的一点:间隙锁的边界由B+树索引结构决定,不是SQL里写的WHERE条件字面值。比如WHERE age > 25,实际锁住的间隙取决于索引中25之后的下一个真实值,而不是数学意义上的“所有大于25的数”。查INFORMATION_SCHEMA.INNODB_LOCKS或SHOW ENGINE INNODB STATUS才能看到真实锁定范围,仅靠语句推断容易误判。











