MySQL在RR隔离级别下,唯一索引等值查询记录不存在时加间隙锁,如id=7不存在且索引有5、10,则锁定(5,10)间隙以防止幻读;RC级别下不加间隙锁,该场景实际无行级锁。

SELECT ... FOR UPDATE 或 UPDATE 在唯一索引上做等值查询、且目标记录不存在时,InnoDB 加的是间隙锁(Gap Lock),不是临键锁,也不是记录锁。
这个行为只在可重复读(REPEATABLE READ)隔离级别下成立;读已提交(READ COMMITTED)下连间隙锁都不会加,只加记录锁——但因为记录不存在,实际不加任何行级锁(可能退化为表级扫描锁,见后文)。
REPEATABLE READ 下唯一索引等值查询不存在时的加锁位置
InnoDB 会定位到「第一个大于查询值」的索引记录,并对该记录前的间隙加锁。
比如索引上有 5, 10, 15,执行 SELECT * FROM t WHERE id = 7 FOR UPDATE:
- 查询值
7不在索引中 - 定位到
10(第一个大于7的记录) - 实际加锁范围是
(5, 10)这个间隙(左开右开),即不允许其他事务插入id = 6, 8, 9等值
这正是防止幻读的关键机制。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 间隙锁只锁「空隙」,不锁记录本身,所以不会阻塞对
id = 10的更新或删除 - 但会阻塞
INSERT INTO t VALUES (6, ...)这类插入 - 如果查询字段没有索引,InnoDB 会全表扫描,每条聚簇索引记录都加记录锁 → 效果等同于锁表
为什么不是临键锁?什么时候会退化?
临键锁 = 记录锁 + 间隙锁,格式是左开右闭,如 (5, 10]。
但在等值查询记录不存在时,InnoDB 明确退化:
- 不锁定
10这条记录本身(所以不是(5, 10]) - 只锁定
(5, 10)这个间隙 - 官方文档和源码验证均指向此行为,不是面试八股编造的
退化的触发条件很明确:
- 查询类型是等值(
=) - 索引是唯一索引(主键或
UNIQUE约束) - 记录在 B+ 树中未命中
- 隔离级别为
REPEATABLE READ
常见踩坑点:非唯一索引 or 无索引
-
非唯一普通索引:即使等值查询不存在,也会加临键锁(
Next-Key Lock),因为无法精确定位“下一个唯一值”,必须向右扫描直到确认无匹配,导致锁住更大范围 -
无索引字段:走全表扫描,每条聚簇索引记录加
X记录锁 → 锁粒度爆炸,极易引发锁等待甚至死锁 -
READ COMMITTED:间隙锁被禁用,该场景下不加任何行级锁(但UPDATE仍需加记录锁,只是没东西可锁,所以实际无锁;而SELECT ... FOR UPDATE在 RC 下可能返回空并释放,不锁间隙)
真正容易被忽略的是:锁行为高度依赖执行计划是否走了索引。explain 一下,确认 type 是 const 或 eq_ref,否则你以为的“唯一等值查询”,实际可能是 ALL 扫描。

















