间隙锁反直觉,是因为它保护的不是已经存在的行,而是索引记录之间未来可能插入的位置。一个查询没有命中任何记录,也可能因为落在某个索引区间里,挡住其他事务插入。

这不是 MySQL 任性,而是 InnoDB 在 RR 隔离级别下为减少幻读付出的代价。理解间隙锁,要顺着索引范围、当前读和 next-key lock 去看,而不是只看业务上“有没有这条数据”。

先把机制边界说清楚

间隙锁锁住的是索引记录之间的空隙,不锁已有记录本身。它主要用于当前读场景,防止其他事务在同一范围内插入新记录,从而破坏范围查询或范围更新的一致性。Next-Key Lock 则把记录锁和间隙锁组合起来。

整体路径

间隙锁的反直觉行为

上面这张图先看入口和边界:宏观上,间隙锁出现在 InnoDB 沿索引范围做当前读或写入时。数据库会根据扫描边界,把已有记录之间的区间锁住。另一个事务如果要往这个区间插入新索引值,就必须等待持有间隙锁的事务结束。

间隙锁导致的死锁场景

第二张图再看结构关系。

底层流程

减轻间隙锁影响的方案对比

底层拆解先看数据结构。「间隙锁深入」至少涉及下面几类结构:

  • 索引有序区间:间隙锁只能存在于索引顺序形成的空隙中。
  • Gap Lock:保护两个索引值之间的插入空间。
  • Next-Key Lock:保护已有记录和它前面的间隙。
  • Insert Intention Lock:插入事务在进入间隙前表达意图。
  • 隔离级别:RR 下范围当前读更容易出现间隙锁。

再看完整执行流程:

  1. 事务在 RR 下执行范围当前读或范围更新。
  2. InnoDB 沿选定索引扫描目标区间。
  3. 对扫描到的记录和记录间隙加 next-key 或 gap lock。
  4. 其他事务尝试在被锁间隙插入记录。
  5. 插入意向锁与间隙锁冲突,插入等待。
  6. 原事务提交后,等待插入继续执行。

取舍与边界

版本差异上,MySQL 8.0 与 5.7 在间隙锁核心语义上保持一致。RC 隔离级别通常减少搜索和索引扫描中的间隙锁,但外键约束检查、唯一性冲突检查等场景仍可能使用间隙锁。版本差异不能替代实际压测。

间隙锁的短板是阻塞范围不总符合业务直觉。一个“不存在”的查询可能锁住未来插入位置;普通索引比唯一索引更容易扩大范围;扫描方向、边界值和数据分布都会影响被锁区间。

典型问题:用机制化例子排查

“没有查到记录却挡住插入”是间隙锁最典型的现象。用一个范围当前读就能复现:只要事务保护了某个索引区间,其他事务往这个区间插入就要等待。

可以落到这些动作:

  • 能用唯一索引精确定位,就不要用普通索引范围当前读兜底。
  • 避免在事务中做大范围 select for update。
  • 先插入唯一约束再处理冲突,往往比先查再插锁范围更小。
  • 确实不需要 RR 语义的业务,可以评估 RC,但要回归一致性场景。

收束:锁住的是未来插入位置

间隙锁最重要的判断不是“有没有这行数据”,而是“这个索引区间是否被保护”。它让 RR 下的范围一致性更稳,也会带来不符合业务直觉的等待。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。

我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。

如果你想继续跟完这套「图解 MySQL」,欢迎关注公众号 「十三Tech」。后续会继续按机制、图解和实战排查这条线更新。

十三Tech公众号二维码