跳到主要内容

InnoDB 锁模型与加锁范围

InnoDB 通过索引记录锁和索引区间锁约束并发写入。分析一条 SQL 会锁住什么,不能只看 WHERE 条件,还要结合隔离级别、访问索引和实际扫描范围。

1. 记录锁锁住索引记录

记录锁作用于索引记录。事务通过二级索引更新一行时,通常既要锁住访问到的二级索引记录,也要锁住对应的聚簇索引记录。

UPDATE orders SET status = 'PAID' WHERE id = 100;

如果 id 是唯一主键,并且查询能精确命中一行,InnoDB 可以只对该索引记录加锁。锁的是索引记录,不是 Java 对象,也不是抽象的业务条件。

2. 间隙锁阻止区间内插入

间隙锁作用于相邻索引记录之间的区间,本身不锁定某条已有记录。它的主要作用是阻止其他事务在该区间插入新索引记录。

例如索引中已有 10 和 20,查询锁住 (10, 20) 的间隙后,其他事务不能在其中插入 15,但仍可能更新 10 或 20,具体要看记录上是否另有锁。

不同事务可以在同一间隙持有看起来冲突的 gap lock,因为它们的目的都是阻止插入,并不是互相排斥地修改一条现有记录。

3. Next-key lock 同时保护记录和前方间隙

Next-key lock 是记录锁与该记录前方间隙锁的组合。InnoDB 在可重复读下执行范围锁定读或更新时,常用 next-key lock 覆盖扫描区间,防止其他事务插入会落入该范围的新记录。

假设索引值为 10、20、30,锁定范围可能按 (-∞, 10](10, 20](20, 30] 等 next-key 区间组织。真实边界仍取决于查询条件与优化器选择的访问路径。

对完整唯一索引进行等值查找时,如果能确认目标记录唯一,通常只需记录锁,不再锁住前方间隙。只使用联合唯一索引的一部分,并不具备这个条件。

4. 插入意向锁协调同一间隙中的插入

事务准备向某个间隙插入时,会先取得插入意向锁。多个事务插入同一间隙中的不同位置时,可以并发进行;若目标位置或间隙被其他事务以不兼容方式锁住,则需要等待。

插入意向锁不是表级的“准备写入”标记。它属于间隙锁的一种,用来表达具体插入位置。

5. 意向锁协调表锁与行锁

InnoDB 在取得行级共享锁或排他锁前,会在表级取得意向共享锁或意向排他锁。它让表锁请求可以快速判断表中是否存在不兼容的行锁,而不必遍历所有记录锁。

普通行级操作之间通常不会因为意向锁彼此阻塞。分析业务锁等待时,主要仍要找到实际的记录锁、间隙锁和持有事务。

6. 隔离级别影响间隙保护

在可重复读下,范围更新、删除和锁定读通常使用 next-key lock,既锁扫描到的记录,也保护相应间隙。普通一致性读使用 MVCC,不会为了读取快照而给这些记录加行锁。

在读已提交下,普通搜索和索引扫描通常不使用间隙锁,只锁匹配的索引记录;外键约束检查和重复键检查等场景仍可能使用间隙锁。更少的间隙锁能提高并发,也允许同一事务的两次查询看到新提交的记录。

7. 扫描路径决定实际加锁范围

SELECT *
FROM orders
WHERE tenant_id = 10 AND created_at >= '2026-08-01'
FOR UPDATE;

若存在 (tenant_id, created_at) 索引,InnoDB 可以锁住对应的连续索引范围。若缺少合适索引,执行计划可能扫描大量记录,并对扫描过程中不能立即排除的记录加锁,实际影响远大于最终返回的行数。

这不等于 InnoDB 自动把行锁升级成表锁。问题在于访问路径太宽,导致大量索引记录被锁住。为锁定读设计索引时,需要同时考虑查询性能和锁范围。

8. 用数据库证据查看锁等待

排查锁等待时可以组合使用:

  • performance_schema.data_locks 查看当前持有或等待的锁。
  • performance_schema.data_lock_waits 查看阻塞关系。
  • SHOW ENGINE INNODB STATUS 查看最近死锁和事务信息。
  • 执行计划确认语句使用了什么索引、预计扫描多少记录。

先找到等待事务、阻塞事务和锁定索引,再回到 SQL 与事务边界。只看到“Lock wait timeout”就增加超时,通常会让连接占用更久。

9. 常见问题

9.1 普通 SELECT 会加行锁吗

InnoDB 的普通一致性读通常通过 MVCC 读取可见版本,不加记录锁。SELECT ... FOR SHARESELECT ... FOR UPDATE 以及更新、删除属于锁定或写操作,会根据访问范围加锁。

9.2 行锁是否一定只影响一行

不一定。“行锁”描述锁的粒度,不代表一条语句只取得一个锁。范围查询、非唯一条件或缺少合适索引时,一条语句可能锁住很多索引记录和间隙。

10. 面试题

10.1 InnoDB 有哪些行级锁,如何判断一条 SQL 的加锁范围

出现公司:美团

考察重点

  • record、gap、next-key 与 insert intention lock。
  • 唯一等值查询和范围查询的差异。
  • 隔离级别、索引访问路径与实际锁范围。

相关内容:第 1 节“记录锁锁住索引记录”至第 8 节“用数据库证据查看锁等待”。

参考回答

InnoDB 的记录锁作用于索引记录,gap lock 阻止在索引间隙中插入,next-key lock 是记录锁与前方间隙锁的组合。插入前还会使用 insert intention lock,表级意向锁用于协调表锁与行锁。

判断加锁范围要看隔离级别和执行计划。可重复读下的范围更新或锁定读通常使用 next-key lock;完整唯一索引的精确查找一般可以退化为记录锁。缺少合适索引时,过宽的访问路径会让语句扫描并锁住大量索引记录,并非发生了锁自动升级。排查时应结合 data_locks、阻塞关系和实际执行计划。