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 都会被阻塞。
相关推荐
lifewange1 小时前
SQL Server、MySQL、Oracle 核心区别对比
数据库·mysql·oracle
彳亍1011 小时前
mysql主从复制和双主复制有什么区别_mysql架构对比
jvm·数据库·python
a7963lin1 小时前
MySQL数据库提示表损坏怎么修复_使用REPAIR TABLE修复方案
jvm·数据库·python
dFObBIMmai1 小时前
如何撤销PUBLIC的危险权限_REVOKE EXECUTE ON UTL_FILE
jvm·数据库·python
2501_901200531 小时前
CSS如何优化移动端CSS选择器性能_遵循BEM规范避免过长嵌套
jvm·数据库·python
ㄟ留恋さ寂寞1 小时前
如何用事务 Transaction 确保 IndexedDB 多表操作的安全性
jvm·数据库·python
m0_495496411 小时前
html标签怎样表示强调_em和i标签语义差异说明【操作】
jvm·数据库·python
weixin_459753941 小时前
如何防止SQL脏数据写入_利用触发器实现强一致性校验
jvm·数据库·python
老纪1 小时前
CSS如何快速预览CSS颜色值效果_结合浏览器开发者工具取色板
jvm·数据库·python