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. 结果 :这等同于锁住了整张表的所有行和所有间隙。在事务提交前,任何其他事务对该表的 INSERTUPDATEDELETE 都会被阻塞。
相关推荐
城管不管7 小时前
重生——第十一次面试之挖财一面2026.8.19已OC
java·服务器·jvm·数据库·spring·面试·职场和发展
倔强的石头1067 小时前
向量数据库从相似度检索走向融合数据底座
数据库
今天AI了吗9 小时前
Python 基础语法(一):常量、变量、输入输出与运算符
开发语言·数据库·人工智能·python·sql·深度学习·机器学习
夜雪一千10 小时前
MySQL 全局锁是什么?原理、风险、备份踩坑完整实战
android·mysql·adb
lilian23310 小时前
Harmony os 技术实战|拼豆制图27:用单字符编码承载 50 张 70×70 图纸
前端·数据库·华为·harmonyos
皮卡丘不断更10 小时前
手机优先的 Personal Ledger:把账目、学习和复盘放到同一个入口
数据库·sqlite·fastapi·开源项目·个人效率
保卫大狮兄11 小时前
设备管理从台账到报废,完整生命周期一次讲清
数据库·设备管理·设备
数据安全技术观察12 小时前
数据动态脱敏:让脱敏策略跟着数据目录走
大数据·网络·数据库
小席是个热心肠12 小时前
Redis的自我学习
数据库·redis·学习
2601_9657984713 小时前
Salient Theme Setup Guide for Fast Creative WordPress Sites
数据库·web3·php·wordpress