这篇博客将基于 MySQL 5.7 及 8.0(8.0.13 及以下版本)的 InnoDB 引擎,深入解析可重复读(Repeatable Read, RR)隔离级别下的加锁逻辑。
在 RR 隔离级别下,InnoDB 为了解决幻读问题,引入了 Next-Key Lock(记录锁 + 间隙锁)。理解其加锁范围,需要遵循以下核心准则:
核心加锁准则
- 原则 1 :加锁的基本单位是 Next-Key Lock ,区间为前开后闭
(prev, curr]。 - 原则 2 :只有查找过程中访问到的对象才会加锁。
- 优化 1 :索引等值查询,给唯一索引 加锁时,Next-Key Lock 退化为行锁(记录需存在)。
- 优化 2 :索引等值查询,向右遍历且最后一个值不满足等值条件时,Next-Key Lock 退化为间隙锁。
- Bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
1. 等值查询:唯一索引与非唯一索引
假设表 t 中已有数据 ID(或索引 c):0, 5, 10, 15, 20, 25。
唯一索引等值查询
- 记录存在 :
where id=10 for update- 根据原则 1,初始锁为
(5, 10]。 - 根据优化 1,退化为 行锁
id=10。
- 根据原则 1,初始锁为
- 记录不存在 :
where id=7 for update- 根据原则 1,定位到 10,初始锁为
(5, 10]。 - 根据优化 2,10 不满足等值条件,退化为 间隙锁
(5, 10)。
- 根据原则 1,定位到 10,初始锁为
非唯一索引等值查询
- 示例 :
select id from t where c=5 lock in share mode;- 步骤 1:定位到 c=5,加 Next-Key Lock
(0, 5]。 - 步骤 2:非唯一索引需向右遍历确认是否有更多 5,访问到 10。
- 步骤 3:根据原则 2,10 被访问,加锁
(5, 10]。 - 步骤 4:根据优化 2,10 不满足等值条件,退化为 间隙锁
(5, 10)。
- 步骤 1:定位到 c=5,加 Next-Key Lock
- 注意 :若是覆盖索引查询且无
for update,主键索引不加锁。
2.范围查询:唯一索引与非唯一索引
InnoDB 范围查询加锁:三步分析法
第一步:确定查询方向(Direction)
- ASC(升序):由左向右扫描。
- DESC(降序):由右向左扫描。
第二步:确定查询起点(Starting Point)
- 动作 :通过 B+ 树搜索(Tree Search) 定位到范围内的第一行。
- 加锁 :根据原则 2(访问即加锁) ,引擎首次访问的该行必须加上 Next-Key Lock (区间为 (prev,curr](prev, curr](prev,curr])。
第三步:确定查询终点(Ending Point)
- 判定 :迭代器持续步进,直到读取到第一个不满足条件的记录(负例)。
- 加锁 :根据原则 2,该"负例"记录因被访问比对,必须加上 Next-Key Lock。
- 唯一索引 Bug:即便在唯一索引上,范围查询也会因 Bug 访问到不满足条件的第一个值为止并加锁。
示例 1:非唯一索引,c >= 15 ORDER BY DESC
假设索引 c 的值为:0, 5, 10, 15, 20, 25, 30。
- 确认方向 :
DESC声明了由右向左(由大到小)的逆向扫描。 - 确认起点 :条件是
c >= 15,在逆向扫描中,起点是索引的逻辑最大值 Supremum。 - 确认终点 :
- 扫描器向左读取 30, 25, 20, 15,均符合条件。
- 由于是非唯一索引,引擎不知道当前的 15 是不是最左侧的 15。
- 为了确认边界,引擎必须继续向左读取,访问到 10。
- 对比发现 10<1510 < 1510<15,扫描终止。
- 加锁结果 :
- 根据"访问即加锁"原则,被"摸到"的记录都要加锁。
- 范围查询不触发"退化为间隙锁"的优化。
- 最终锁住 :Supremum 记录、30, 25, 20, 15,以及 **10 的 Next-Key Lock
(5, 10]**。
示例 2:非唯一索引,c >= 15 ORDER BY ASC
- 确认方向 :
ASC(默认)声明了由左向右(由小到大)的正向扫描。 - 确认起点 :树搜索定位第一个满足条件
c >= 15的记录,即第一个 15。 - 确认终点 :条件只有下限没有上限,引擎会一直向右扫描,直到索引的最末端 Supremum 记录。
- 加锁结果:
- 从 15 开始,向右访问到的所有记录及其间隙均加锁。
- 最终锁住 :15, 20, 25, 30 的 Next-Key Lock 以及 Supremum 的 Next-Key Lock。
示例 3:唯一索引,c >= 15 ORDER BY DESC
这里触及了 PDF 中提到的那个关键 Bug。
- 确认方向 :
DESC逆向扫描。 - 确认起点 :索引逻辑最大值 Supremum。
- 确认终点 :
- 虽然是唯一索引,理论上读到 15 就可以停止。
- 但根据 规则 5(Bug):唯一索引上的范围查询会访问到不满足条件的第一个值为止。
- 引擎依然会向左多读取一个 10。
- 加锁结果 :
- 因为扫描器"多看了一眼" 10,根据原则 2,10 必须加锁。
- 由于是范围查询,10 的锁不会退化。
- 最终锁住 :Supremum, 30, 25, 20, 15,以及 **10 的 Next-Key Lock
(5, 10]**。
3. 无索引查询:性能灾难
当 WHERE 条件字段没有索引时(如 update t set d=d+1 where c=7):
- 全表扫描:由于没有索引加速,引擎被迫扫描主键索引(聚簇索引)的所有记录。
- 全面封锁 :根据原则 2,扫描过程中访问到的每一行记录及其之间的间隙都会被加上 Next-Key Lock。
- 结果 :这等同于锁住了整张表的所有行和所有间隙。在事务提交前,任何其他事务对该表的
INSERT、UPDATE或DELETE都会被阻塞。