这篇博客将基于 MySQL 5.7 及 8.08.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或索引c0, 5, 10, 15, 20, 25。唯一索引等值查询记录存在where id10 for update根据原则 1初始锁为(5, 10]。根据优化 1退化为行锁id10。记录不存在where id7 for update根据原则 1定位到 10初始锁为(5, 10]。根据优化 210 不满足等值条件退化为间隙锁(5, 10)。非唯一索引等值查询示例select id from t where c5 lock in share mode;步骤 1定位到 c5加 Next-Key Lock(0, 5]。步骤 2非唯一索引需向右遍历确认是否有更多 5访问到 10。步骤 3根据原则 210 被访问加锁(5, 10]。步骤 4根据优化 210 不满足等值条件退化为间隙锁(5, 10)。注意若是覆盖索引查询且无for update主键索引不加锁。2.范围查询唯一索引与非唯一索引InnoDB 范围查询加锁三步分析法第一步确定查询方向DirectionASC升序由左向右扫描。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 15ORDER BYDESC假设索引c的值为0, 5, 10, 15, 20, 25, 30。确认方向DESC声明了由右向左由大到小的逆向扫描。确认起点条件是c 15在逆向扫描中起点是索引的逻辑最大值Supremum。确认终点扫描器向左读取 30, 25, 20, 15均符合条件。由于是非唯一索引引擎不知道当前的 15 是不是最左侧的 15。为了确认边界引擎必须继续向左读取访问到10。对比发现101510 151015扫描终止。加锁结果根据“访问即加锁”原则被“摸到”的记录都要加锁。范围查询不触发“退化为间隙锁”的优化。最终锁住Supremum 记录、30, 25, 20, 15以及 **10 的 Next-Key Lock(5, 10]**。示例 2非唯一索引c 15ORDER BYASC确认方向ASC默认声明了由左向右由小到大的正向扫描。确认起点树搜索定位第一个满足条件c 15的记录即第一个15。确认终点条件只有下限没有上限引擎会一直向右扫描直到索引的最末端Supremum记录。加锁结果从 15 开始向右访问到的所有记录及其间隙均加锁。最终锁住15, 20, 25, 30 的 Next-Key Lock 以及Supremum 的 Next-Key Lock。示例 3唯一索引c 15ORDER BYDESC这里触及了 PDF 中提到的那个关键Bug。确认方向DESC逆向扫描。确认起点索引逻辑最大值Supremum。确认终点虽然是唯一索引理论上读到 15 就可以停止。但根据规则 5Bug唯一索引上的范围查询会访问到不满足条件的第一个值为止。引擎依然会向左多读取一个10。加锁结果因为扫描器“多看了一眼” 10根据原则 210 必须加锁。由于是范围查询10 的锁不会退化。最终锁住Supremum, 30, 25, 20, 15以及 **10 的 Next-Key Lock(5, 10]**。3. 无索引查询性能灾难当WHERE条件字段没有索引时如update t set dd1 where c7全表扫描由于没有索引加速引擎被迫扫描主键索引聚簇索引的所有记录。全面封锁根据原则 2扫描过程中访问到的每一行记录及其之间的间隙都会被加上Next-Key Lock。结果这等同于锁住了整张表的所有行和所有间隙。在事务提交前任何其他事务对该表的INSERT、UPDATE或DELETE都会被阻塞。