gap lock在rr隔离级别下自动生效,是innodb防止幻读的核心机制;它并非独立锁,而是next-key lock中锁定索引间隙的部分,仅作用于非唯一索引或无索引的范围查询,通过阻塞insert intention lock来禁止间隙插入。

Gap Lock 在 RR 隔离级别下自动生效,不是可选功能
MySQL 默认的 REPEATABLE READ 隔离级别下,InnoDB 只要执行当前读(SELECT FOR UPDATE、UPDATE、DELETE、INSERT),且查询条件落在非唯一索引或无索引字段上,就会加 Next-Key Lock(记录锁 + 间隙锁)。这不是 bug,是防幻读的设计代价。
常见错误现象:INSERT 卡住不报错、SHOW ENGINE INNODB STATUS 里看到 lock_mode X locks gap before rec insert intention waiting —— 这说明新插入的值落在了别人已锁定的间隙里,而非数据冲突。
- 唯一索引(含主键)上的等值查询(如
WHERE id = 123)且记录存在 → 只加记录锁,不加 Gap Lock - 非唯一索引上的等值查询(如
WHERE status = 'pending')→ 会锁住整个status = 'pending'对应的索引段,范围可能极大 - 没走索引的查询(全表扫描)→ InnoDB 退化为隐式聚簇索引,Gap Lock 覆盖不可预测的大范围
INSERT 被阻塞的本质是插入意向锁与 Gap Lock 冲突
INSERT 执行前必须先申请 Insert Intention Lock,这是一种特殊的 Gap Lock,作用是声明“我要往这个间隙插”。而另一个事务若已持有同一间隙的 Gap Lock(比如刚执行过 SELECT * FROM t WHERE status = 'pending' FOR UPDATE),InnoDB 就会直接拒绝,不等待、不重试,立刻阻塞或报死锁。
关键点在于:两者锁的是同一个空隙。比如已有主键值 10 和 30,间隙 (10, 30) 就是冲突目标区域——前者说“不准插”,后者说“我要插”,InnoDB 必须立刻裁决,否则锁等待链会失控。
- 这种冲突在
INSERT ... SELECT或批量插入时更隐蔽,因为扫描行为可能触发隐式间隙锁定 - 多个事务并发插入相同唯一键(如
INSERT INTO t (a,b) VALUES (4,11)),还会叠加 S 锁等待,形成 A→B→A 死锁闭环 -
INSERT IGNORE和ON DUPLICATE KEY UPDATE同样要走唯一性校验流程,底层仍会申请锁,一样中招
降级到 READ COMMITTED 是最快解法,但有条件
把隔离级别设为 READ COMMITTED 后,InnoDB 默认禁用 Gap Lock(外键和唯一约束检查除外),所有写操作只加精确的记录锁 + 插入意向锁,UPDATE t SET x=1 WHERE status='pending' 就只锁住实际命中的行,不再锁整个状态区间。
实操前必须确认四件事:
- 连接初始化时执行:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;或 JDBC URL 加sessionVariables=transaction_isolation='READ-COMMITTED' -
binlog_format必须是ROW(SHOW VARIABLES LIKE 'binlog_format'),否则主从可能不一致 - 表上有
UNIQUE KEY或FOREIGN KEY时,冲突检测仍会触发隐式 Gap Lock,INSERT相同唯一值依然会阻塞 - 业务代码里避免“读-判-写”逻辑(如先查
COUNT(*)再插入),RC 下两次读结果可能不同,得改用INSERT ... ON DUPLICATE KEY UPDATE或应用层幂等控制
不改隔离级别时,靠索引设计压缩锁范围
Gap Lock 锁的是索引树上的“间隙”,所以索引越精准,被锁的物理区间就越小。单列 status 索引会让 WHERE status = 'pending' 锁住整个该值段;而联合索引 (status, create_time) 能把锁压缩到几秒内的时间窗口。
真正难处理的是:Gap Lock 本身不可见,它藏在几行之前某个看似无害的 SELECT FOR UPDATE 或范围更新里。你看到的只是 INSERT 卡住,或者 Deadlock found,但根源不在插入语句本身。
- 用
performance_schema.data_locks(MySQL 8.0+)查LOCK_MODE = 'GAP'或'NEXT-KEY'的记录,看LOCK_DATA字段显示的边界(如(5, 10))是否覆盖你要插的值 -
LOCK_DATA为空或NULL?大概率是缺索引导致的全表扫描,得补索引 - 对高并发插入场景,优先用自增主键;若必须用业务主键(如订单号),确保生成逻辑尽量有序,减少间隙重叠概率











