Skip to content

MySQL 数据库的 MVCC 机制

约 1075 字大约 4 分钟

关系型数据库

2025-03-18

MySQL 作为一个多线程的数据库,支持客户端对其的并发查询,并且将其默认的隔离级设置为可重复读。那么在并发的操作中 MySQL 是如何隔离各个事务的呢?它实际上使用的是 MVCC 这种机制来管理并发访问,实现了不同版本之间数据的隔离。

MVCC 的主要作用是允许多个事务同时读取同一行数据而不会彼此阻塞,每个事务看到的数据都是其本身事务开始时的数据版本,即便可能中途有其他事务对其进行了修改。这意味着这种机制可以实现可重复读的隔离级别。

Read View

其实我们不难想到,想要达成上面提到的这种效果只需要在事务开始的时候为该事务的所有的数据建立一个快照,在事务执行期间都读取此快照的数据就可以排除其他事务的干扰。这其实就是 MVCC 中 Read View 的原理。Read View 就是一个数据快照,只不过它不是简单地将所有数据拷贝一份。

  • 读已提交隔离级别是在每个 SELECT 语句执行前都生成一个 Read View。
  • 可重复读隔离级别是在事务执行第一条 SELECT 时生成一个 Read View,然后在整个事务期间都使用这一个 Read View。

刚刚有提到 Read View 并不是简单地保存一份数据的备份,它其实是记录了创建那一刻的所有事务详情,包含了四个重要的字段:

  • m_ids。指创建 Read View 的时候,当前数据库中活跃事务 的事务 id 列表。
  • min_trx_id。指创建 Read View 时活跃事务中最小的事务 id,即 m_ids 中的最小值。
  • max_trx_id。指创建 Read View 时当前数据库准备分配给下一个事务的 id,即当前全局最大的事务 id + 1。
  • creator_trx_id。指创建 Read View 的事务的事务 id。
Read View
Read View

InnoDB 聚簇索引隐藏列

刚刚讲完了事务部分的快照,现在讲一下 MVCC 中的另一部分:隐藏列。只要数据存储的时候使用了 InnoDB,也就是 MySQL 的默认存储引擎,它的聚簇索引都会包含下面两个隐藏列:

  • trx_id。记录了最后一个改动这行聚簇索引记录的事务 id。
  • roll_pointer。每次对某条聚簇索引进行改动后,都会将写入 undo log 中的旧版本数据的指针写入该列。故该列会形成一个单向链,可以通过这个指针找到每一个旧版本的数据。
InnoDB 隐藏列
InnoDB 隐藏列

实现原理

现在我们已经知道事务启动时生成的 Read View 是如何记录事务的状态,又知道了 InnoDB 隐藏列是如何记录数据的更改历史的。那么现在我们只需要知道事务在运行的过程中是如何根据这两个信息来判断某一行数据是否能被自己的读到的。我们可以分三种情况来讨论:

  • trx_id > min_trx_id。这表示这个版本的记录是在创建 Read View 前已经提交了的,所以该版本的数据对当前事务是可见的。
  • trx_id \geq max_trx_id。这表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以对当前事务不可见。
  • trx_id \in (min_trx_id, max_trx_id)。此时需要判断 trx_id 是否在 m_ids 列表中:
    • 如果在列表中,表示生成该版本记录的活跃事务依然活跃着,则该版本数据对当前事务不可见。
    • 如果不在,则可见。

MVCC 的版本控制是针对于行来说的。在同一行中,所有列的可见权限是一致的。不存在对于一个事务,一行中的部分列可见,而部分不可见的情况。

贡献者

更新日志

2025/3/25 01:41
查看所有更新日志
  • c2f26-improve(docs): adjust tags
  • e1af2-feat(docs): add new article

Keep It Simple