MySQL InnoDB 加锁全解析

这篇博客将基于 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。
  • 记录不存在 :where id=7 for update
    • 根据原则 1,定位到 10,初始锁为 (5, 10]。
    • 根据优化 2,10 不满足等值条件,退化为 间隙锁 (5, 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)。
  • 注意 :若是覆盖索引查询且无 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。

  1. 确认方向 :DESC 声明了由右向左(由大到小)的逆向扫描。
  2. 确认起点 :条件是 c >= 15,在逆向扫描中,起点是索引的逻辑最大值 Supremum。
  3. 确认终点 :
    • 扫描器向左读取 30, 25, 20, 15,均符合条件。
    • 由于是非唯一索引,引擎不知道当前的 15 是不是最左侧的 15。
    • 为了确认边界,引擎必须继续向左读取,访问到 10。
    • 对比发现 10<1510 < 1510<15,扫描终止。
  4. 加锁结果 :
    • 根据"访问即加锁"原则,被"摸到"的记录都要加锁。
    • 范围查询不触发"退化为间隙锁"的优化。
    • 最终锁住 :Supremum 记录、30, 25, 20, 15,以及 **10 的 Next-Key Lock (5, 10]**。

示例 2:非唯一索引,c >= 15 ORDER BY ASC

  1. 确认方向 :ASC(默认)声明了由左向右(由小到大)的正向扫描。
  2. 确认起点 :树搜索定位第一个满足条件 c >= 15 的记录,即第一个 15。
  3. 确认终点 :条件只有下限没有上限,引擎会一直向右扫描,直到索引的最末端 Supremum 记录。
  4. 加锁结果:
  • 从 15 开始,向右访问到的所有记录及其间隙均加锁。
  • 最终锁住 :15, 20, 25, 30 的 Next-Key Lock 以及 Supremum 的 Next-Key Lock。

示例 3:唯一索引,c >= 15 ORDER BY DESC

这里触及了 PDF 中提到的那个关键 Bug。

  1. 确认方向 :DESC 逆向扫描。
  2. 确认起点 :索引逻辑最大值 Supremum。
  3. 确认终点 :
    • 虽然是唯一索引,理论上读到 15 就可以停止。
    • 但根据 规则 5(Bug):唯一索引上的范围查询会访问到不满足条件的第一个值为止。
    • 引擎依然会向左多读取一个 10。
  4. 加锁结果 :
    • 因为扫描器"多看了一眼" 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):

  1. 全表扫描:由于没有索引加速,引擎被迫扫描主键索引(聚簇索引)的所有记录。
  2. 全面封锁 :根据原则 2,扫描过程中访问到的每一行记录及其之间的间隙都会被加上 Next-Key Lock。
  3. 结果 :这等同于锁住了整张表的所有行和所有间隙。在事务提交前,任何其他事务对该表的 INSERT、UPDATE 或 DELETE 都会被阻塞。
相关推荐
Elastic 中国社区官方博客4 小时前
将你自己的密钥用于现有 Elastic Cloud 部署
大数据·数据库·elasticsearch·全文检索
红海云5 小时前
Jev:给智能系统做判断的模型
大数据·数据库·人工智能
wjkjpcba5 小时前
PCBA烧录程序是什么:PCBA包工包料厂家解析烧录与测试
linux·数据库·人工智能·smt贴片加工·pcba贴片加工厂
꯭自꯭闭꯭5 小时前
达梦事物特性及MVCC
linux·运维·数据库
小马同学-5 小时前
MySQL主从复制和读写分离
数据库·mysql
谢亮_vipxieliang5 小时前
Spring 事务失效的常见场景
java·开发语言·数据库·spring boot
海绵宝宝转agent6 小时前
MySql高频面试八股开源笔记总结
mysql·面试·开源
geovindu6 小时前
sql: JSON and XML Data Handling in SQL using sql server 2025
大数据·数据库·sqlserver·数据库开发·数据库架构
IT大白鼠7 小时前
图数据库系列 · 第 02 篇——架构拆解:原生图存储到因果集群
数据库·架构·nosql
余槐i7 小时前
数据没回滚也不报错:@Transactional 自调用失效的 3 种复现与修复
spring boot·mysql·多线程·spring 事务