对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含下面两个必要的隐藏列:
trx_id:一个事务每次对某条聚簇索引记录进行改动时,都会把该事务的事务 id 赋值给 trx_id 隐藏列;roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo 日志中。这个隐藏列就相当于一个指针,可以通过它找到该记录修改前的信息。比如,表 account 中现在包含一条记录:
mysql> SELECT * FROM account WHERE id = 1;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | kelvin | 15 |
+----+--------+---------+
1 row in set (0.01 sec)
假设插入该记录的事务 id 为80,那么此刻该条记录的示意图如下图所示:

假设之后有两个事务 id 分别为100、200的事务对这条记录进行 UPDATE 操作:
UPDATE account SET name='vicky' WHERE id = 1; // 事务id为100
UPDATE account SET name='peppa' WHERE id = 1; // 事务id为200
每对记录进行一次改动,都会记录一条 undo 日志。每条 undo 日志也都有一个 roll_pointer 属性,通过这个属性可以将这些 undo 日志串成一个链表,如下图所示:

在每次更新该记录后,都会将旧值放到一条 undo 日志中(就算是该记录的一个旧版本)。
随着更新次数的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,这个链表称为版本链。
版本链的头节点就是当前记录的最新值。
我们会利用这个记录的版本链来控制并发事务访问相同记录时的行为,我们把这种机制称之为多版本并发控制(Multi-Version Concurrency Control, MVCC)。
对于使用 READ COMMITTED 和 REPEATABLE READ 隔离级别的事务来说,都必须保证读到已经提交的事务修改过的记录。
也就是说,假如另一个事务已经修改了记录但是尚未提交,则不能直接读取最新版本的记录。
核心问题是:需要判断版本链中的哪个版本是当前事务可见的。数据库设计了 ReadView(一致性视图)来解决此问题。
ReadView 主要包含4个比较重要的内容:
m_ids:在生成 ReadView 时,当前系统中活跃的读写事务的事务id列表。min_trx_id:在生成 ReadView 时,当前系统中活跃的读写事务中最小的事务id;也就是 m_ids 中的最小值。max_trx_id:在生成 ReadView 时,系统应该分配给下一个事务的事务id值。creator_trx_id:生成该 ReadView 的事务的事务id。有个 ReadView 之后,在访问某条记录时,只需要按下面步骤判断记录的某个版本是否可见:
Read COMMITTED:每次读取数据前都生成一个 ReadViewREPEATABLE READ:在第一次读取数据时生成一个 ReadView所谓的 MVCC 指的就是在使用 READ COMMITTED、REPEATABLE READ 这两种隔离级别的事务执行普通的 SELECT操作时,访问记录的版本链的过程。这样可以使不同事务的 读-写、写-读操作并发执行,从而提升系统性能。
READ COMMITTED、REPEATABLE READ 这两个隔离级别有一个很大的不同,就是生成 ReadView 的时机不同:READ COMMITTED 在每一次进行普通 SELECT 操作前都会生成一个 ReadView;而 REPEATABLE READ 只在第一次进行普通 SELECT 操作前生成一个 ReadView,之后的查询操作都重复使用这个 ReadView。
只有我们进行普通的 SELECT 查询时,MVCC 才生效。