记录锁只会锁住已经存在的索引记录,间隙锁则用于锁定两个索引值之间的空白区间以阻止新数据插入;当使用非唯一索引进行等值查询时,二者通常会组合成临键锁。要准确判断 MySQL InnoDB 当前使用的是哪种锁,需要通过SHOW ENGINE INNODB STATUS确认,而且具体加锁方式还会受到事务隔离级别与索引类型的共同影响。

记录锁只锁已存在的索引行,不会锁定索引间隙
记录锁(Record Lock)是 InnoDB 中真正意义上“锁某一行”的最小粒度锁,但它锁住的并不是物理数据行本身,而是该行在**聚簇索引或二级索引中的对应索引记录**。只要 SQL 条件能够命中索引,尤其是主键索引或唯一索引,并且精确匹配到某条已存在的数据,InnoDB 通常只会加记录锁。
常见触发场景:
SELECT * FROM users WHERE id = 10 FOR UPDATE;——id是主键,命中后只锁该条索引记录UPDATE orders SET status = 'paid' WHERE order_no = 'ORD-2026-001';——order_no是唯一索引,也只会锁对应记录
关键点:如果没有走索引,MySQL InnoDB 的加锁范围可能显著扩大;如果查询没有找到目标记录,也不会产生记录锁——此时更有可能出现的是间隙锁。
间隙锁只锁“空白区间”,核心作用是防止插入新数据
间隙锁(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(一致性非锁定读)通常不会加这种锁。
等值查询非唯一索引时,记录锁和间隙锁往往会同时出现
这是 MySQL 锁机制实战中最容易让人困惑的地方之一。只要查询条件基于非唯一索引进行等值匹配(例如 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 记录,实际上连这些记录之间的空隙,以及前后相邻索引值之间的区域也一起被锁住了。这也是为什么 MySQL 中看似简单的等值查询,在高并发场景下反而容易造成插入阻塞。
判断当前到底加了哪种锁,要看 SHOW ENGINE INNODB STATUS 输出
执行完相关加锁 SQL 后,可以在另一个会话中运行 SHOW ENGINE INNODB STATUSG,重点查看 TRANSACTIONS 段中的 RECORD LOCKS 相关内容:
- 包含
locks rec but not gap→ 表示纯记录锁 - 包含
locks gap before rec→ 表示间隙锁(或临键锁中的间隙部分) - 同时出现
locks rec but not gap和locks gap before rec→ 通常说明表现为临键锁
不要只依赖 explain 或执行计划去推测锁类型——真正可靠的判断依据,是 INNODB STATUS 中展示的锁结构信息,这才是 MySQL 实际加锁行为的直接证据。
最容易被忽略的一点是:间隙锁是否会出现,根本上取决于事务隔离级别和索引类型,而不只是 SQL 写法本身。比如同样一句 WHERE age = 25,在 READ COMMITTED 下,可能根本不会触发间隙锁;但如果切换到 REPEATABLE READ,就有可能把整段空白索引区间一起锁住。因此在系统上线前,一定要在目标数据库隔离级别下实际验证锁行为,这一步非常关键,不能省略。
