首页/数据/SQL 速查/间隙锁(Gap Lock)与幻读抑制
变更与事务52 条

间隙锁(Gap Lock)与幻读抑制

InnoDB 在 REPEATABLE READ 下用 next-key 锁(记录锁+间隙锁)锁住范围,防住别的事务在区间内插入;PostgreSQL 没有间隙锁。

SQL 速查数据更新于 2026-10-13

间隙锁是怎么来的

默认隔离级别下,InnoDB 做范围扫描时不仅锁住命中的记录,还把记录之间的「空隙」一起锁上,这套组合锁的作用是阻止其他事务往区间里插入新行,从而压住幻读。PostgreSQL 没有间隙锁这个概念,它靠多版本快照与读一致性来应对幻读,写冲突则交给后续的冲突检测。理解这一点,才能明白为什么同一段并发逻辑在两个数据库上的等待行为完全不同。

基本信息

适用平台MySQL 8.0(InnoDB)/ PostgreSQL 12 及以上
MySQL 机制范围锁定与部分等值查找会锁住记录间的空隙
PostgreSQL 机制只做行级锁,幻读依靠快照与串行化冲突检测处理
关闭条件MySQL 在较低隔离级别下间隙锁基本关闭,但外键与唯一键检查仍会用
主要代价并发正确性提高的同时,事务之间更容易相互等待

典型使用场景

  • 区间批量处理:按编号区间锁定一批订单再逐个处理
  • 编号占用校验:先查区间再插入,防止唯一编号被并发抢用
  • 排查锁等待:定位为什么两条不相干的写入在互相阻塞
  • 隔离级别评估:从默认级别降到读已提交前,评估幻读风险
  • 从 MySQL 迁到 PostgreSQL 时重新验证并发写入逻辑

观察与验证

观察与验证
-- MySQL:范围锁定读,区间内的插入会被阻塞
BEGIN;
SELECT * FROM orders
WHERE id BETWEEN 100 AND 200
FOR UPDATE;
-- 另一个事务在该区间插入新行会等待,直到本事务提交或回滚
COMMIT;

-- 观察当前锁等待情况
SELECT * FROM performance_schema.data_locks;      -- 当前持有的锁
SELECT * FROM performance_schema.data_lock_waits; -- 正在等待的锁

-- PostgreSQL:范围读取不会阻塞插入,需要重新设计并发校验
BEGIN;
SELECT * FROM orders WHERE id BETWEEN 100 AND 200 FOR UPDATE;
-- 该语句只锁已存在的行,区间内新增的行走不到这里的保护
COMMIT;

锁行为对照

场景MySQL InnoDBPostgreSQL
按区间加锁读取锁定命中行与记录间空隙只锁定已存在的行
区间内并发插入被阻塞,直到持锁事务结束不被阻塞,可以插入
唯一索引等值命中命中已存在记录时只加记录锁只加行锁
幻读防护依赖间隙锁依赖快照与串行化检测
锁等待排查有专门的锁信息表可查用系统视图与等待事件排查
用非索引条件做锁定读取会扩大锁定范围,严重时近似锁住整张表:执行计划显示全表扫描时,几乎所有行都会被加上锁。加锁查询前先看执行计划,确认走的是索引。
把 MySQL 的间隙锁经验直接搬到 PostgreSQL 会造成误判:PG 的范围读取不阻塞插入,原本依赖「锁住区间防止重复插入」的逻辑必须改用唯一约束或显式冲突检测来实现。

易错点

  • 在 PostgreSQL 上照搬依赖间隙锁的并发校验逻辑,区间内并发插入不被阻塞,出现重复数据
  • 对未走索引的条件加锁读取,锁定范围扩大到近似全表,其他业务写入被大面积阻塞
  • 降低隔离级别当作性能优化手段,却忽略了幻读风险,业务侧的重复插入校验失效
  • 排查锁等待时只看语句不看执行计划,误判为「锁粒度太大」,实际是索引缺失
  • 长事务持锁期间做慢操作(调用外部接口、等待人工确认),把锁等待放大成线上故障

数据来源与参考

  • MySQL 8.0 Reference Manual · InnoDB Locking
  • PostgreSQL Documentation · Explicit Locking
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌