3. MySQL InnoDB 锁机制详解:Record Lock、Gap Lock、Next-Key Lock、插入意向锁,以及它们如何影响并发
InnoDB 的锁机制是保障事务隔离性与数据一致性的核心,也是排查锁等待与死锁问题的理论基础。本章将深入剖析四种关键锁类型:Record Lock(记录锁)、Gap Lock(间隙锁)、Next-Key Lock(临键锁) 以及 插入意向锁(Insert Intention Lock),并阐明它们如何共同影响数据库的并发行为。
3.1 锁的基本分类与作用范围
InnoDB 的锁可以按锁定粒度与逻辑功能进行分类。下表梳理了核心锁类型及其作用范围:
| 锁类型 | 锁定范围 | 默认场景 | 主要作用 |
|---|---|---|---|
| Record Lock | 单条索引记录 | 唯一索引等值查询命中记录 | 防止其他事务修改或删除该记录 |
| Gap Lock | 索引记录之间的间隙 | 非唯一索引等值查询、范围查询 | 防止其他事务在间隙中插入新记录 |
| Next-Key Lock | 记录 + 记录之前的间隙 | 普通索引范围查询、默认行锁实现 | 防止幻读(Phantom Read) |
| 插入意向锁 | 间隙中的插入点 | INSERT 操作准备插入时 | 协调插入操作,减少锁冲突 |
关键认知: Next-Key Lock 是 InnoDB 在 REPEATABLE READ 隔离级别下的默认行锁实现,它本质上是 Record Lock 与 Gap Lock 的组合。理解这一点是分析锁等待的前提。
3.2 Record Lock(记录锁)
Record Lock 锁定的是索引记录本身。即使表中没有显式定义索引,InnoDB 也会使用隐式的聚簇索引(Row ID)来加锁。
- 触发条件: 使用唯一索引(包括主键)进行等值查询,且查询命中唯一记录时,InnoDB 会将 Record Lock 降级为仅锁定该记录。
- 并发影响: 只阻塞其他事务对该记录的 UPDATE、DELETE 或
SELECT ... FOR UPDATE操作;不阻塞对间隙的插入操作。
-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE id = 100 FOR UPDATE; -- 对 id=100 的记录加 Record Lock
-- 事务 B(被阻塞)
UPDATE users SET name = 'new' WHERE id = 100; -- 等待事务 A 释放锁
-- 事务 C(不被阻塞)
INSERT INTO users (id, name) VALUES (99, 'ok'); -- 间隙未被锁定,可正常插入
3.3 Gap Lock(间隙锁)
Gap Lock 锁定的是索引记录之间的“间隙”,即两个相邻索引记录之间的开放空间,也包括第一个记录之前和最后一个记录之后的间隙。
- 触发条件: 在 REPEATABLE READ 隔离级别下,使用非唯一索引或范围条件进行查询时,InnoDB 会对扫描到的间隙加 Gap Lock。
- 并发影响: 只阻塞其他事务在间隙中插入新记录,不阻塞对已有记录的修改。
常见误区: Gap Lock 是“防插入”锁,不是“防修改”锁。很多锁等待排查中,INSERT 语句被阻塞,往往是因为目标间隙被其他事务的 Gap Lock 锁定,而不是因为记录本身被锁定。
-- 假设表 t 有索引列 age,数据分布:10, 20, 30
-- 事务 A
START TRANSACTION;
SELECT * FROM t WHERE age = 15 FOR UPDATE; -- 未命中记录,锁定间隙 (10, 20)
-- 事务 B(被阻塞)
INSERT INTO t (age) VALUES (16); -- 试图插入间隙 (10,20),被 Gap Lock 阻塞
-- 事务 C(不被阻塞)
UPDATE t SET name = 'x' WHERE age = 20; -- 修改已有记录,不被阻塞
3.4 Next-Key Lock(临键锁)
Next-Key Lock 是 Record Lock 与 Gap Lock 的组合,锁定范围是“一个索引记录及其之前的间隙”。例如,锁定记录 age=20 时,实际锁定的是 (10, 20] 这个区间。
- 触发条件: 普通索引(非唯一索引)的范围查询或等值查询,默认使用 Next-Key Lock。
- 并发影响: 既能防止其他事务修改该记录,也能防止在记录之前的间隙中插入新记录,从而彻底防止幻读。
下图展示了 Next-Key Lock 在索引上的锁定范围:
3.5 插入意向锁(Insert Intention Lock)
插入意向锁是一种特殊的间隙锁,它在 INSERT 操作准备插入记录时自动获取。与普通 Gap Lock 不同,插入意向锁之间是互不阻塞的,多个事务可以同时在同一间隙中持有插入意向锁,只要它们插入的位置不同。
- 触发条件: INSERT 语句在插入前,会对目标间隙加插入意向锁。
- 并发影响: 插入意向锁只与 Gap Lock 冲突,不与另一个插入意向锁冲突。这意味着,如果间隙中没有 Gap Lock,多个 INSERT 可以并发执行。
实战价值: 在锁等待排查中,如果看到
INSERT ... waiting for insert intention lock,说明目标间隙被其他事务的 Gap Lock 或 Next-Key Lock 锁定。此时应检查持有 Gap Lock 的事务是否正在执行范围查询或未提交的 SELECT ... FOR UPDATE。
-- 场景:事务 A 持有间隙锁,事务 B 等待插入意向锁
-- 事务 A
START TRANSACTION;
SELECT * FROM t WHERE age BETWEEN 20 AND 30 FOR UPDATE; -- 锁定间隙 (20,30)
-- 事务 B(被阻塞)
INSERT INTO t (age) VALUES (25); -- 等待插入意向锁,因为间隙 (20,30) 被事务 A 锁定
-- 事务 C(不被阻塞,如果插入点不同)
INSERT INTO t (age) VALUES (35); -- 插入间隙 (30,40),不被阻塞
3.6 四种锁的冲突矩阵
下表展示了不同锁类型之间的冲突关系,帮助快速判断锁等待原因:
| 持有锁 \ 请求锁 | Record Lock | Gap Lock | Next-Key Lock | 插入意向锁 |
|---|---|---|---|---|
| Record Lock | 冲突 | 不冲突 | 冲突 | 不冲突 |
| Gap Lock | 不冲突 | 不冲突 | 不冲突 | 冲突 |
| Next-Key Lock | 冲突 | 不冲突 | 冲突 | 冲突 |
| 插入意向锁 | 不冲突 | 不冲突 | 不冲突 | 不冲突 |
排查要点: 从冲突矩阵可以看出,Gap Lock 和 Next-Key Lock 是阻塞 INSERT 操作的主要元凶。当 INSERT 语句出现锁等待时,优先排查是否存在未提交的范围查询或
SELECT ... FOR UPDATE 事务。
3.7 锁机制对并发的影响总结
四种锁在并发场景下的影响可以归纳为以下几点:
- Record Lock: 粒度最细,并发度最高,仅阻塞对同一记录的写操作。
- Gap Lock: 牺牲部分插入并发性以换取幻读保护,适用于非唯一索引的等值查询。
- Next-Key Lock: 最严格的锁,同时保护记录和间隙,是 REPEATABLE READ 隔离级别的默认行为。
- 插入意向锁: 专为 INSERT 设计,允许多个插入操作在同一间隙中并发,前提是不与 Gap Lock 冲突。
下图展示了四种锁在索引结构上的作用范围对比:
学习建议: 在排查锁等待时,先通过
SHOW ENGINE INNODB STATUS 查看锁类型,再对照冲突矩阵判断阻塞链。熟练掌握这四种锁的语义,是成为 InnoDB 锁问题排查高手的第一步。