InnoDB对于MVCC的实现

多版本并发控制(MVCC,Multi-Version Concurrency Control)是 InnoDB 实现高并发的核心机制,其核心目标是在不加锁(或低锁)的前提下,让不同事务看到符合隔离级别的数据版本,既保证读 - 写、写 - 读并发不阻塞,又能解决脏读、不可重复读、幻读等一致性问题

一、核心概念:一致性非锁定读 vs 锁定读

在讲解 MVCC 实现前,需先明确 InnoDB 的两种读模式,这是 MVCC 落地的基础:

1. 一致性非锁定读(Consistent Non-Locking Read)

  • 定义 :InnoDB 默认的读方式,读取数据时不加锁,而是通过读取数据的历史版本(快照)来保证一致性,因此不会阻塞写操作,写操作也不会阻塞该读操作。
  • 适用场景:普通 SELECT 语句(无 FOR UPDATE/LOCK IN SHARE MODE)。
  • 核心依赖:MVCC 的快照机制(ReadView + undo-log)。

2. 锁定读(Locking Read)

  • 定义 :读取数据时会对目标行加锁,强制后续写操作等待,保证读 - 写的强一致性,不依赖 MVCC 的快照
  • 适用场景 :需要保证读取的数据后续能安全修改的场景,如:
    • SELECT ... FOR UPDATE:加排他锁(X 锁),阻止其他事务修改或加共享锁;
    • SELECT ... LOCK IN SHARE MODE:加共享锁(S 锁),阻止其他事务修改,但允许加共享锁。
  • 核心依赖:InnoDB 的行锁机制(Record Lock)、间隙锁(Gap Lock)、Next-key Lock。

二、InnoDB 实现 MVCC 的核心组件

MVCC 的本质是为每行数据维护多个版本,事务根据隔离级别读取 "可见的版本",其实现依赖三大核心组件:隐藏字段undo-logReadView

1. 隐藏字段(Hidden Columns)

InnoDB 会为每张表的每行数据(除主键外)自动添加 3 个隐藏字段,用于标记数据版本和归属事务:

隐藏字段 数据类型 作用说明
DB_TRX_ID 6 字节 记录最后一次修改该行数据的事务 ID(插入 / 更新 / 删除均算修改)。
DB_ROLL_PTR 7 字节 回滚指针,指向该行数据的上一个版本(存储在 undo-log 中),形成版本链。
DB_ROW_ID 6 字节 可选字段,仅当表无主键 / 唯一非空键时生成,作为行的唯一标识(类似自增 ID)。

示例 :假设表user有显式字段id (PK)name,则实际存储结构为:

DB_ROW_ID id name DB_TRX_ID DB_ROLL_PTR
1 1 张三 100 指向 undo-log 版本 1

当事务 ID=200 的事务更新name为 "李四" 时,InnoDB 不会直接修改原行,而是:

  1. 保留原行,DB_TRX_ID仍为 100;
  2. 新增一行新版本数据,DB_TRX_ID=200DB_ROLL_PTR指向原行(版本 1);
  3. 原行的DB_ROLL_PTR无变化(成为版本链的尾节点)。

2. Undo Log(回滚日志)

Undo Log 是 InnoDB 的事务日志之一,核心作用:

  • 事务回滚:若事务执行失败,通过 undo-log 恢复数据到修改前状态;
  • MVCC 版本链 :存储数据的历史版本,与DB_ROLL_PTR配合形成 "版本链"。

(1)Undo Log 的类型

  • INSERT Undo Log:仅用于 INSERT 操作,事务提交后可直接删除(因为 INSERT 的行仅当前事务可见,无历史版本共享);
  • UPDATE/DELETE Undo Log:用于 UPDATE/DELETE 操作,事务提交后不能立即删除,需保留至所有依赖该版本的事务都已提交(通过 purge 线程清理过期版本)。

(2)版本链的形成

每行数据的多个版本通过DB_ROLL_PTR串联,最新版本在链头,最旧版本在链尾。例如:

复制代码
最新版本(trx_id=300) → DB_ROLL_PTR → 版本2(trx_id=200) → DB_ROLL_PTR → 版本1(trx_id=100) → null

3. ReadView(读视图)

ReadView 是事务执行一致性非锁定读时生成的 "可见性判断快照",本质是一个数据结构,记录了当前系统中 "活跃的未提交事务 ID",用于判断版本链中的哪个版本对当前事务可见。

ReadView 包含的核心字段

字段 作用说明
m_ids 当前系统中活跃的未提交事务 ID 列表(升序排列)。
min_trx_id m_ids中的最小值(最小活跃事务 ID)。
max_trx_id 系统下一个要分配的事务 ID(大于所有已分配的事务 ID)。
creator_trx_id 生成该 ReadView 的事务 ID(当前事务 ID)。

数据可见性算法(核心规则)

对于版本链中的某一行版本(记其DB_TRX_IDtrx_id),InnoDB 通过以下规则判断是否对当前事务可见:

  1. trx_id == creator_trx_id:该版本是当前事务自己修改的,可见;
  2. trx_id < min_trx_id:该版本由已提交的事务生成(事务 ID 小于最小活跃 ID,说明已提交),可见;
  3. trx_id >= max_trx_id:该版本由未来的事务生成(事务 ID 未分配),不可见;
  4. min_trx_id ≤ trx_id < max_trx_id
    • trx_idm_ids中(该事务仍活跃未提交),不可见;
    • trx_id不在m_ids中(该事务已提交),可见。

示例 :假设 ReadView 的min_trx_id=200max_trx_id=500m_ids=[200,300,400]creator_trx_id=150

  • 版本trx_id=100< min_trx_id,可见;
  • 版本trx_id=200:在m_ids中,不可见;
  • 版本trx_id=450:不在m_ids< max_trx_id,可见;
  • 版本trx_id=500>= max_trx_id,不可见;
  • 版本trx_id=150:等于creator_trx_id,可见。

三、RC 和 RR 隔离级别下 MVCC 的核心差异

InnoDB 的隔离级别(读未提交 RU、读已提交 RC、可重复读 RR、串行化 SERIALIZABLE)中,RU 不使用 MVCC(直接读最新数据),SERIALIZABLE 使用锁定读,只有 RC 和 RR 依赖 MVCC,核心差异在于ReadView 的生成时机

1. 读已提交(RC,Read Committed)

  • ReadView 生成时机每次执行 SELECT 语句时都生成新的 ReadView
  • 核心表现
    • 解决脏读:只能读取已提交事务的版本;
    • 无法解决不可重复读:同一事务内多次 SELECT,每次生成新的 ReadView,可能读取到其他事务提交的新版本;
    • 幻读未解决:多次 SELECT 可能看到新插入的行(因为每次 ReadView 不同)。

RC 下 MVCC 示例(不可重复读场景)

时间 事务 A(RC 隔离级) 事务 B
T1 BEGIN;
T2 SELECT name FROM user WHERE id=1; → 张三(生成 ReadView1:m_ids=[A 的 ID])
T3 BEGIN; UPDATE user SET name=' 李四 ' WHERE id=1; COMMIT;(trx_id=B 的 ID)
T4 SELECT name FROM user WHERE id=1; → 李四(生成 ReadView2:m_ids=[A 的 ID],B 的 ID 已提交,可见)

2. 可重复读(RR,Repeatable Read)

  • ReadView 生成时机仅在事务中第一次执行 SELECT(一致性非锁定读)时生成 ReadView,后续所有 SELECT 复用该 ReadView
  • 核心表现
    • 解决脏读、不可重复读:同一事务内多次 SELECT 复用同一个 ReadView,只能看到第一次 SELECT 时已提交的版本;
    • 基础 MVCC 无法完全解决幻读,需结合 Next-key Lock。
RR 下 MVCC 示例(解决不可重复读)
时间 事务 A(RR 隔离级) 事务 B
T1 BEGIN;
T2 SELECT name FROM user WHERE id=1; → 张三(生成 ReadView1:m_ids=[A 的 ID],复用至事务结束)
T3 BEGIN; UPDATE user SET name=' 李四 ' WHERE id=1; COMMIT;(trx_id=B 的 ID)
T4 SELECT name FROM user WHERE id=1; → 张三(复用 ReadView1,B 的 ID 在 ReadView1 中属于 "未来事务",不可见)

四、MVCC 解决不可重复读的原理

不可重复读的定义:同一事务内多次读取同一行数据,结果不一致(因其他事务修改并提交)。

MVCC 解决该问题的核心逻辑:

  1. RR 隔离级:事务第一次 SELECT 生成 ReadView 后,后续所有 SELECT 复用该 ReadView;
  2. 其他事务提交的新版本,其trx_id要么大于 ReadView 的max_trx_id(未来事务),要么在m_ids中(活跃事务),根据可见性算法判定为 "不可见";
  3. 事务始终读取 ReadView 生成时已提交的版本,因此多次读取结果一致,解决不可重复读。

注意:RC 隔离级因每次 SELECT 生成新 ReadView,无法解决不可重复读。

五、MVCC + Next-key Lock 防止幻读

幻读的定义:同一事务内多次执行相同范围的 SELECT,结果集行数不一致(因其他事务插入 / 删除符合条件的行)。

1. 仅 MVCC 无法解决幻读(RR 隔离级)

即使 RR 下复用 ReadView,若其他事务插入新行并提交,新行的trx_id可能小于 ReadView 的min_trx_id(已提交),导致事务再次 SELECT 时看到新行(幻读)。

2. Next-key Lock(临键锁)的补充作用

Next-key Lock 是 InnoDB 在 RR 隔离级下默认的行锁策略,结合了Record Lock(行锁)Gap Lock(间隙锁)

  • Record Lock:锁定索引行本身;
  • Gap Lock:锁定索引行之间的间隙(防止插入新行);
  • Next-key Lock:锁定 "索引行 + 间隙",覆盖当前行和下一个间隙。

3. MVCC + Next-key Lock 解决幻读的逻辑

  • 读操作(一致性非锁定读):通过 MVCC 的 ReadView 保证多次读取的版本一致;
  • 写操作(INSERT/UPDATE/DELETE):通过 Next-key Lock 锁定范围,阻止其他事务插入 / 删除符合条件的行;
  • 两者结合:
    1. 读时:MVCC 保证只能看到事务启动时已提交的版本,看不到其他事务新增的行;
    2. 写时:Next-key Lock 阻止其他事务在锁定范围内新增 / 删除行,从根本上避免幻读。

示例(RR 隔离级 + Next-key Lock 防幻读)

时间 事务 A 事务 B
T1 BEGIN; SELECT * FROM user WHERE age > 20;(生成 ReadView1,同时加 Next-key Lock 锁定 age>20 的范围)
T2 BEGIN; INSERT INTO user (age) VALUES (25);(被 Next-key Lock 阻塞,无法插入)
T3 SELECT * FROM user WHERE age > 20; → 结果与 T1 一致(无幻读)
T4 COMMIT;(释放锁)
T5 INSERT 成功

六、总结

InnoDB 的 MVCC 实现是隐藏字段、undo-log、ReadView 三大组件的协同:

  1. 隐藏字段:标记数据版本和版本链指针;
  2. undo-log:存储历史版本,形成版本链;
  3. ReadView:根据隔离级别生成快照,通过可见性算法判断数据版本是否可见;
  4. 隔离级别差异:RC 每次 SELECT 生成 ReadView(不可重复读),RR 仅第一次生成(可重复读);
  5. 防幻读:RR 下 MVCC 保证读一致性,Next-key Lock 阻止写操作修改范围数据,两者结合彻底解决幻读。
相关推荐
Macbethad2 小时前
SpringMVC RESTful API开发技术报告
java·spring boot·后端
05大叔2 小时前
SpringMVCDay01
java·开发语言
AC赳赳老秦2 小时前
农业智能化:DeepSeek赋能土壤与气象数据分析,精准预测病虫害,守护丰收希望
java·前端·mongodb·elasticsearch·html·memcache·deepseek
纟 冬2 小时前
Flutter & OpenHarmony 运动App运动提醒组件开发
android·java·flutter
雪花desu2 小时前
【Hot100-Java简单】:两数之和 (Two Sum) —— 从暴力枚举到哈希表的思维跃迁
java·数据结构·算法·leetcode·哈希表
leaves falling2 小时前
c语言打印闰年
java·c语言·算法
我的xiaodoujiao2 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 37--测试报告 Allure 前置步骤-配置安装 JDK 详细图文教程
java·开发语言·学习·测试工具
老华带你飞2 小时前
婚纱摄影网站|基于java + vue婚纱摄影网站系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
Gofarlic_OMS2 小时前
从Adobe到SolidWorks:研发设计软件资产管理的现状分析
数据库·安全·adobe·oracle·金融·区块链