记录锁只锁已存在的索引行,间隙锁锁定两个索引值之间的空隙防止插入,等值查询非唯一索引时二者结合形成临键锁;锁类型须通过SHOW ENGINE INNODB STATUS确认,且受隔离级别与索引类型决定。

记录锁只锁已存在的索引行,不碰间隙
记录锁(Record Lock)是真正“锁住某一行”的最小粒度锁,但它锁的不是数据行本身,而是该行在**聚簇索引或二级索引上的对应索引记录**。只要查询条件能走索引(尤其是主键或唯一索引),且精准命中某条已存在的记录,InnoDB 就只加记录锁。
常见触发场景:
-
SELECT * FROM users WHERE id = 10 FOR UPDATE;——id是主键,命中即锁该索引记录 -
UPDATE orders SET status = 'paid' WHERE order_no = 'ORD-2026-001';——order_no是唯一索引,也只锁对应记录
关键点:没走索引?直接退化为表锁;查不到记录?根本不会加记录锁——这时候可能转为间隙锁。
间隙锁只锁“空的地方”,防止插入新数据
间隙锁(Gap Lock)不锁任何实际数据行,它锁定的是**两个索引值之间的空隙**,区间表示为左开右开,比如 (10, 20)。它的唯一目的就是阻止其他事务往这个空隙里 INSERT 新记录,从而避免幻读。
典型触发条件:
- 隔离级别必须是
REPEATABLE READ(READ COMMITTED下默认禁用间隙锁) - 查询使用了**非唯一索引**或**范围条件**,且扫描范围覆盖了间隙
- 例如:
SELECT * FROM users WHERE age BETWEEN 25 AND 35 FOR UPDATE;—— 即使表中没有age=28的记录,也会锁住(25, 35)这个间隙
注意:SELECT ... FOR UPDATE 和 UPDATE/DELETE 都可能触发,但普通 SELECT(无锁读)不会。
等值查询非唯一索引时,记录锁和间隙锁会同时出现
这是最容易混淆的实战坑点。当查询条件基于**非唯一索引**做等值匹配(如 WHERE status = 'pending'),InnoDB 不会只加记录锁——它会为每个匹配到的索引项加记录锁,并额外在这些项的前后间隙加间隙锁,最终组合成临键锁(Next-Key Lock)。
举例说明:
- 表
orders有非唯一索引idx_status(status),当前存在status='pending'的记录对应索引值:20、25、30 - 执行
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE; - 实际加锁范围包括:
(15,20]、(20,25]、(25,30]、(30,35](假设相邻索引值为 15/35)
也就是说,你以为只锁了三条 pending 记录,其实连它们之间的所有空隙、甚至前一个和后一个索引值之间的区域都一起锁了。这也是为什么看似简单的等值查询,在高并发下反而容易阻塞插入。
判断当前加的是哪种锁,看 SHOW ENGINE INNODB STATUS 输出
运行锁相关语句后,在另一个会话执行 SHOW ENGINE INNODB STATUS\G,重点关注 TRANSACTIONS 段里的 RECORD LOCKS 行:
- 含
locks rec but not gap→ 纯记录锁 - 含
locks gap before rec→ 间隙锁(或临键锁中的间隙部分) - 含
locks rec but not gap+ 同时出现locks gap before rec→ 实际是临键锁行为
别依赖 explain 或执行计划来猜锁类型——只有 INNODB STATUS 里的锁结构描述才是真实加锁证据。
最常被忽略的一点:间隙锁的存在完全依赖隔离级别和索引类型,而不是 SQL 写法本身。同一句 WHERE age = 25,在 READ COMMITTED 下可能不加间隙锁,在 REPEATABLE READ 下却可能锁住大片空白区间。上线前务必在目标隔离级别下实测锁行为。


















