MySQL防止幻读靠临键锁(Next-Key Lock),即记录锁+间隙锁组合,仅在可重复读隔离级别、索引范围查询时生效;间隙锁不单独存在,而是临键锁中防止插入的关键部分。

MySQL 防止幻读,靠的不是“间隙索引”,而是**间隙锁(Gap Lock)和临键锁(Next-Key Lock)协同作用**,且必须在特定条件下才生效。所谓“间隙索引”并不存在——真正起作用的是**索引结构 + 查询方式 + 隔离级别**三者配合形成的锁行为。
临键锁才是防幻读的核心机制
临键锁 = 记录锁(Record Lock)+ 间隙锁(Gap Lock),它锁定的不是一个点,而是一个“左开右闭”的区间,比如 (10, 20]。这意味着:
- 既锁住已存在的记录(如 id=20 这一行)
- 也锁住该记录前面的空档(如 id 在 10 到 20 之间所有未被占用的位置)
- 其他事务想 INSERT id=15 的新行时,需先申请 Insert Intention Lock,但该意向锁与已有的 Gap 锁冲突 → 被阻塞
这种组合锁只在 REPEATABLE READ 隔离级别 下、对 走索引的范围查询(如 WHERE id > 10、BETWEEN、IN 等)自动启用。
间隙锁不单独存在,但它是临键锁的“防插”部分
间隙锁本身不能独立加锁,它永远依附于临键锁出现。它的作用很明确:堵住“能插入但还没插入”的位置。例如:
- 表中有 id=5、12、25 三行,执行 SELECT * FROM t WHERE id > 10 FOR UPDATE
- InnoDB 实际加两个临键锁:(10,12] 和 (12,25],外加 (25,+∞]
- 其中 (10,12)、(12,25)、(25,+∞) 这些开区间,就是间隙锁覆盖的范围,阻止 INSERT id=11、18、30 等操作
注意:普通快照读(SELECT 不带锁)不触发间隙锁,靠 MVCC 避免幻读;只有当前读(SELECT ... FOR UPDATE / UPDATE / DELETE)才依赖它堵插入漏洞。
什么情况下这些锁会失效?
临键锁非常“挑剔”,稍有偏差就退化为仅锁行、甚至不锁间隙:
- 查询条件没走索引(如 WHERE status = 'pending',但 status 无索引)→ 全表扫描,间隙锁不生效
- 等值查询主键(WHERE id = 100)→ 只加记录锁,不锁间隙
- 使用函数或表达式(WHERE YEAR(created_at) = 2024)→ 索引失效,间隙锁失效
- 隔离级别设为 READ COMMITTED → InnoDB 完全不启用间隙锁,幻读必然发生
- MySQL 8.0+ 中残留 innodb_locks_unsafe_for_binlog = ON → 会禁用间隙锁
怎么确认锁真的起了作用?
不能只看 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ 就认为万事大吉。验证方法更可靠:
- 查 performance_schema.data_locks:找 LOCK_MODE 含 NEXT-KEY 或 GAP 的记录
- 运行 SHOW ENGINE INNODB STATUS\G,在 TRANSACTIONS 段查看 lock_mode 是否含 gap
- 在一个事务中执行 SELECT ... FOR UPDATE 范围查询后,另起会话尝试 INSERT 到该范围 —— 若被卡住,说明临键锁生效
不复杂但容易忽略。


















