
核心思想:我们活在“不同版本”的世界里
想象一下,你和你的同事们在同时编辑一个共享的云文档。
- 你在写第一章。
- 同事A在修改第五章。
- 同事B刚开始准备写第十章。
为了不让大家互相干扰(比如你看到同事A写了一半的、不通顺的句子),系统必须给你一个“稳定”的版本看。你开始编辑时文档是什么样,在你编辑的过程中它就应该一直是什么样,不应该被别人的修改所干扰。
数据库也面临同样的问题。这张图解释的,就是数据库(特指像 MySQL InnoDB 这样的数据库)如何实现这种“稳定版本”的机制。这个机制的专业术语叫做 MVCC (Multi-Version Concurrency Control),即“多版本并发控制”。
第一部分:数据是怎么存放的?(看图的上方表格)
我们先看图最上面的表格部分。这里有一张表,有 name 和 age 两个我们关心的字段。但是请注意,在数据库的底层,每一行数据后面还悄悄跟着几个额外的信息:
DB_TRX_ID: 事务ID。可以理解为“这次修改是由谁完成的”。每一次对数据库的操作(比如修改“李四”的年龄)都会被分配一个独一无二的、递增的编号,这就是事务ID。DB_ROLL_PTR: 回滚指针。可以理解为“指向前一个版本的链接”。它指向这条数据被修改之前的样子。undo log: 历史版本记录本。当数据被修改时,旧的版本不会被立刻删除,而是被存放到一个叫做undo log的地方。DB_ROLL_PTR指针就指向这里。
我们来看图中的例子:
- 最开始,
李四的年龄是28,这次修改的事务ID是10。 - 后来,有人把
李四的年龄改成了38。这次修改的事务ID是11。 - 此时,数据库里最新的数据是
李四, 38, 事务ID=11。但是,它的回滚指针指向了它之前的版本,也就是存放在undo log里的李四, 28, 事务ID=10。
这就形成了一个版本链。通过回滚指针,数据库可以一路回溯,找到这条数据所有的历史版本。就像一个文档的“修改历史”功能。
小结 1: 数据库中的每一行数据,都可能存在一个由新到旧的“版本链”,新版本指向旧版本。
第二部分:一致性的“快照”(看图的中间核心部分)
现在,一个新的事务(我们称之为“我的事务”)开始了,它想要读取数据。问题来了:数据库里同时有很多人在修改数据,有些修改已经完成了(我们称之为“已提交”),有些还在进行中(“未提交”)。“我的事务”应该看到哪个版本的数据呢?
为了解决这个问题,在“我的事务”开始的那一刻,数据库会为它生成一个“快照”(Read View)。
这个“快照”就像你按下了暂停键,给当前所有事务的状态拍了一张照片。这张照片记录了:
m_ids: 拍照时,有哪些事务正在运行(还没提交)。图中的例子是[11, 12, 13, 14]。up_limit_id: 正在运行的事务中,最小的ID是什么。图中的例子是11。所有比11还小的ID(比如10),肯定在拍照前就已经提交了,所以它们做的修改是“可见”的。low_limit_id: 拍照后,下一个将要分配的事务ID是什么。图中的例子是15。所有ID大于等于15的事务,都是在拍照后才开始的,它们做的修改对我来说是“不可见”的。
你可以把这个“快照”想象成一个**“照妖镜”**,当我的事务去读取数据时,它会用这个镜子去照一下数据的版本,来判断这个版本我该不该看。
小结 2: 每个事务开始时,都会获得一个“快照”,这个快照定义了哪些事务的修改对我是可见的,哪些是不可见的。
第三部分:判断规则——我到底能看见谁?
现在,我们拿着这个“照妖镜”(快照),去读取 李四 这条数据。数据库会执行以下判断流程:
-
首先拿到最新的版本:
李四, 38, 事务ID=11。 -
开始用“照妖镜”判断:这个版本的事务ID是
11。- 规则一:是不是比
up_limit_id(11) 小?- 不是,
11不小于11。所以不能直接确定它是可见的。
- 不是,
- 规则二:是不是比
low_limit_id(15) 大或等于?- 不是,
11小于15。所以不能直接确定它是不可见的。
- 不是,
- 规则三(关键!):既然在
up_limit_id和low_limit_id之间,那就查一下“正在运行事务列表”m_ids([11, 12, 13, 14])。11在这个列表里!这意味着,在我拍照的那一刻,事务11还在运行,没有提交。- 结论: 对于“我的事务”来说,事务
11的修改是“不可见的未来”,我不能看。
- 规则一:是不是比
-
怎么办?
- 既然最新版本不能看,数据库就会顺着这条数据的
回滚指针(DB_ROLL_PTR),找到它的上一个版本。 - 上一个版本是:
李四, 28, 事务ID=10。
- 既然最新版本不能看,数据库就会顺着这条数据的
-
对上一个版本,重复用“照妖镜”判断:这个版本的事务ID是
10。- 规则一:是不是比
up_limit_id(11) 小?- 是的,
10小于11! - 结论: 这说明事务
10在我拍照之前早就完成了。它的修改对我来说是“确定的历史”,是可见的!
- 是的,
- 规则一:是不是比
-
最终结果:所以,“我的事务”最终读到的数据是
李四的年龄为28。
通过这个机制,即使在我读取数据期间,有其他事务(比如事务 11)提交了,我也看不到它的修改,保证了我看到的数据在我整个事务期间都是一致的。
总结一下
这张图的核心逻辑可以概括为:
- 版本链(Undo Log):修改数据时,旧版本被保留下来,形成一条历史版本链。这是实现“穿越”的基础。
- 快照(Read View):事务开始时,拍下一张“哪些事务已完成,哪些在进行”的照片。这是“穿越”的规则。
- 可见性判断:读取数据时,从最新版本开始,用“快照”的规则逐个检查,如果不符合可见性规则,就通过版本链回溯到上一个版本再判断,直到找到一个符合规则的可见版本为止。
这个精妙的设计,就是数据库能够在成千上万个用户同时读写时,依然能保证数据正确、互不干扰的秘密武器。

被折叠的 条评论
为什么被折叠?



