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 在索引上的锁定范围:

Next-Key Lock 锁定范围示意图 age=10 age=20 age=30 age=40 间隙 (10,20) 间隙 (20,30) 间隙 (30,40) Next-Key Lock (20, 30] 说明: 当查询条件涉及 age=30 时,InnoDB 锁定 (20,30] 区间。 其他事务既不能修改 age=30 的记录,也不能在 (20,30) 间隙中插入新记录。

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 LockGap LockNext-Key Lock插入意向锁
Record Lock冲突不冲突冲突不冲突
Gap Lock不冲突不冲突不冲突冲突
Next-Key Lock冲突不冲突冲突冲突
插入意向锁不冲突不冲突不冲突不冲突
排查要点: 从冲突矩阵可以看出,Gap Lock 和 Next-Key Lock 是阻塞 INSERT 操作的主要元凶。当 INSERT 语句出现锁等待时,优先排查是否存在未提交的范围查询或 SELECT ... FOR UPDATE 事务。

3.7 锁机制对并发的影响总结

四种锁在并发场景下的影响可以归纳为以下几点:

  1. Record Lock: 粒度最细,并发度最高,仅阻塞对同一记录的写操作。
  2. Gap Lock: 牺牲部分插入并发性以换取幻读保护,适用于非唯一索引的等值查询。
  3. Next-Key Lock: 最严格的锁,同时保护记录和间隙,是 REPEATABLE READ 隔离级别的默认行为。
  4. 插入意向锁: 专为 INSERT 设计,允许多个插入操作在同一间隙中并发,前提是不与 Gap Lock 冲突。

下图展示了四种锁在索引结构上的作用范围对比:

四种锁在索引上的作用范围对比 记录 A 记录 B 记录 C 记录 D Record Lock 锁定记录 B Gap Lock 间隙 Next-Key Lock 锁定间隙 + 记录 C 插入意向锁 点 Record Lock 锁定单条记录;Gap Lock 锁定间隙;Next-Key Lock 锁定间隙+记录; 插入意向锁锁定间隙中的插入点,多个插入意向锁可共存。
学习建议: 在排查锁等待时,先通过 SHOW ENGINE INNODB STATUS 查看锁类型,再对照冲突矩阵判断阻塞链。熟练掌握这四种锁的语义,是成为 InnoDB 锁问题排查高手的第一步。