news 2026/6/6 9:00:06

深度拆解:从 Read View 到 Undo Log,多版本并发控制(MVCC)的底层确定性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度拆解:从 Read View 到 Undo Log,多版本并发控制(MVCC)的底层确定性

摘要

在关系型数据库(如 MySQL InnoDB)的高并发场景下,“读写冲突”是调优面临的最常见瓶颈。如果为了保证数据一致性而对读写操作全部加锁(如强行使用串行化读),系统的吞吐量将发生灾难性下跌。为了实现“读不加锁,读写不冲突”,现代主流存储引擎普遍采用了MVCC(Multi-Version Concurrency Control,多版本并发控制)机制。本文将从多版本链表、Undo Log 演进以及 Read View 结构出发,深度剖析 MVCC 在事务隔离中的底层实现。

一、 数据行的隐蔽面孔:聚集索引中的隐藏列

在 InnoDB 存储引擎中,你在表里看到的每一行记录(Row),在底层的 B+ 树聚集索引中除了存放你自定义的列数据之外,还会被编译器强制附加三个极其关键的系统隐藏列

隐藏列名称占用空间核心职责
DB_TRX_ID6 字节事务 ID。记录最后一次插入或修改该行数据的事务系统标识。
DB_ROLL_PTR7 字节回滚指针。指向该行数据上一个版本的Undo Log记录,是构建历史版本的时空纽带。
DB_ROW_ID6 字节行单调自增 ID。仅在表没有显式指定主键或唯一索引时由内核自动生成。

这三个隐藏列是实现事务回滚与多版本控制的物理底座。

二、 时光回溯的通道:Undo Log 版本链

每当一个事务尝试修改一条记录时,为了支持并发事务读取历史数据(以及本事务回滚),存储引擎不会直接将旧数据覆盖并抹去,而是会遵循以下串联逻辑:

  1. 加锁改写:事务 A 对该行记录加锁,准备修改。

  2. 写 Undo 日志:把该行记录当前的旧版本值原封不动地复制到一块专门的内存/磁盘区域——Undo Log(回滚日志)中。

  3. 更新记录与指针:修改聚集索引页中该行记录的实际值,将DB_TRX_ID改为事务 A 的 ID,并让DB_ROLL_PTR物理指向刚刚在 Undo Log 中生成的那个旧版本节点。

随着多个并发事务交错执行修改,原本孤立的一行数据在底层就会通过DB_ROLL_PTR指针,被拉平并串联成一条由新到旧的单向链表。这条链表,就是 MVCC 赖以生存的“多版本时光链”。

三、 快照读的数学边界:Read View(快照视图)

有了多版本链表,当一个并发的“只读事务”发起读取请求时(在快照读/Consistent Read 场景下),它到底应该看链表中的哪一个版本?这就需要通过Read View来进行边界判定。

Read View 是在事务发起查询时,由事务管理器动态创建的一个内存结构,它主要包含以下四个核心字段:

  • m_ids:在当前这一时刻,整个数据库系统中还未提交的、活跃的事务 ID 列表。

  • min_trx_idm_ids列表中最小的事务 ID。

  • max_trx_id:系统即将分配给下一个新事务的 ID 值(即当前最大事务 ID + 1)。

  • creator_trx_id:生成当前这个 Read View 的只读事务自身的事务 ID。

核心可见性判定算法(数学状态机)

当只读事务遍历该行数据的 Undo Log 版本链时,它会取出当前版本的DB_TRX_ID(假设为trx_id),并严格带入以下四条红线进行比对:

Plaintext

┌───────────────────────────┬─────────────────────────────┬───────────────────────────┐ │ trx_id < min_trx_id │ min_trx_id <= trx_id ... │ trx_id >= max_trx_id │ ├───────────────────────────┼─────────────────────────────┼───────────────────────────┤ │ 已提交事务:绝对可见 │ 活跃或未提交:判断是否在 │ 未来事务:绝对不可见 │ │ │ m_ids 列表中 │ │ └───────────────────────────┴─────────────────────────────┴───────────────────────────┘
  1. trx_id<min_trx_id: 说明生成这个版本的事务在当前只读事务开启前已经完全提交了。结论:该版本数据可见

  2. trx_id≥max_trx_id: 说明生成这个版本的事务是在当前只读事务开启之后才启动的(属于未来的事务)。结论:该版本数据绝对不可见

  3. trx_id=creator_trx_id: 说明这个版本就是当前只读事务自己修改的。结论:自己看自己的修改,必然可见

  4. min_trx_id≤trx_id<max_trx_id: 此时需要进一步检索m_ids数组:

    • 如果trx_idm_ids列表中:说明生成这个版本的事务目前还处于活跃状态(还没提交)。结论:不可见

    • 如果trx_id不在m_ids列表中:说明生成这个版本的事务虽然 ID 很大,但在当前查询发起前已经完成了提交。结论:可见

如果判定为“不可见”,只读事务就会顺着DB_ROLL_PTR指针向下寻找上一个更老版本的 Undo Log 节点,再次带入算法比对,直到找到第一个可见的版本为止。

四、 隔离级别的本质:Read View 的创建时机

MVCC 机制的奇妙之处在于,通过精细调控 Read View 的创建时机,可以用完全相同的底层代码完美实现 SQL 标准中的两种核心隔离级别:

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

在 RC 隔离级别下,事务中每一次执行SELECT语句时,都会重新、独立地生成一个全新的 Read View。 这意味着,如果另一个并发写事务在两次SELECT之间提交了数据,第二次SELECT生成的 Read View 里的m_ids就会剔除掉这个写事务 ID。根据算法,写事务的修改变得可见,这就实现了“读已提交”,但同时也导致了“不可重复读”的发生。

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

在 RR 隔离级别下,事务只有在第一次执行SELECT时才会生成一个 Read View,并且在整个事务的生命周期内一直复用这个视图。 即使后续有其他写事务提交了,由于当前事务手中的 Read View 已经固化,活跃事务列表m_ids没有任何变化。因此,无论执行多少次查询,顺着版本链推导出的结果都完全一致,在底层完美阻断了不可重复读的发生。

五、 总结

  1. MVCC 是现代主流数据库存储引擎(如 InnoDB)消除读写锁竞争、最大化并发吞吐吞吐量的核心引擎。

  2. 通过向聚集索引行记录强制追加隐藏列,配合向后追加写的 Undo Log,在物理上编织出了一条严密的数据时空追溯链条。

  3. Read View 结构通过最小活跃事务 ID、未来事务上限等数学边界,实现了高效的可见性过滤,并在内核级别通过调节 Read View 的刷新时机,轻量级地撑起了 RC 与 RR 隔离级别的物理隔离防线。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 8:57:03

Java 后端转行 AI 大模型,这份技能差距评测报告请收好

三年 Java 老兵的转型焦虑&#xff1a;大模型风口下的真实技能差距 在技术圈摸爬滚打三年&#xff0c;你可能已经熟练掌握了 Spring Boot 的微服务架构&#xff0c;对 JVM 调优如数家珍&#xff0c;甚至能徒手画出复杂的分布式系统时序图。然而&#xff0c;当"AI 大模型”…

作者头像 李华
网站建设 2026/6/6 8:41:56

Llama 3深度解析:能力可验证的开源大模型工程实践

1. 项目概述&#xff1a;这不是又一个“开源大模型”&#xff0c;而是一次能力边界的重新丈量“Meta LLAMA 3 — Most Capable Open LLM”这个标题&#xff0c;乍看像一句营销口号&#xff0c;但如果你过去两年深度参与过中文社区的模型选型、本地部署或应用开发&#xff0c;你…

作者头像 李华
网站建设 2026/6/6 8:40:03

MOOC数据科学课程为何教不会工业级数据处理

1. 这不是抱怨&#xff0c;是数据科学教育现场的实操诊断报告“Data Science MOOCs are too Superficial”——这句话我第一次在2018年旧金山一场小型数据工程师聚会里听到时&#xff0c;台下十来个人全笑了。不是笑它夸张&#xff0c;而是笑它太准&#xff1a;像被戳中了脊椎骨…

作者头像 李华