MySQL

简单了解 MySQL MVCC(多版本并发控制)

MVCC(多版本并发控制)是 MySQL 中的一种重要机制,用于提高数据库的并发性能,了解 MVCC 的基本概念、工作原理以后或许有帮助。

#MySQL#数据库#MVCC#SQL

Tip

这篇文章很简短,不打算太深入,主要了解一下 MySQL 中 MVCC(多版本并发控制)的基本概念和工作原理,有个基础的了解,以后见到 MVCC 有个印象就行。

MVCC 概念

MVCC(多版本并发控制)是数据库里常用的一个控制并发的方法,它的核心思想是 通过保留数据的多个版本,让读写操作可以同时进行,大幅提升系统性能。

传统锁机制下,读数据可能要加锁,会阻止别人写;写数据也会阻止别人读,但 MVCC 允许读写同时发生,互不影响,实现了读写不冲突。

处理方式

MVCC 的逻辑简单理解就是:每当事务要改数据时,数据库不会直接把老数据抹掉改成新的,而是给这行数据创建一个“快照”,每个快照都带着修改它的事务 ID。

这样一来,不同的事务在同一时间访问同一行数据,看到的可能完全不是同一个东西,这取决于你什么时候开始的,以及你的隔离级别是怎么设定的。

  • 读数据时:事务会拿到一张“快照”,哪怕在你读的过程中,别人把数据改得天花乱坠,你看到的依然是启动那一刻快照的样子(注意哈:要看什么隔离级别,这里仅供解释说明,理解即可)。
  • 写数据时:系统会生成新版本并记录下你的事务 ID,老版本会先留在 Undolog(撤销日志)里,直到确定没有任何事务再需要它了,后台的清理机制才会把它回收掉。

MVCC 的具体实现

MySQL 的 InnoDB 引擎使用 MVCC 来实现 “可重复读”(Repeatable Read)隔离级别,主要是用到了这两个关键东西实现的: 版本链ReadView

每行数据后面其实都藏了两个隐藏字段:一个是最后修改它的事务 IDDB_TRX_ID),另一个是指向旧版本数据的指针DB_ROLL_PTR),这些旧版本串在一起,就成了一条版本链。

当你执行查询时,InnoDB 会为你生成一个 ReadView,这就像是一张“未完成事务清单”,查询时,系统会顺着版本链往回找,如果某个版本的事务 ID 在清单里(说明还没提交),那就不能看,继续往回找,直到找到一个已经提交的版本。

例子

我们来看一个具体的例子来理解这个过程,先熟悉下演示的背景:

假设有一行数据,原本是 (id=1, value='A'),由事务 100 提交。

  1. 事务 200 进来,把 value 改成了 'B'(未提交)
  2. 事务 300 紧接着把它改成了 'C'(未提交)
  3. 这时,你的事务 400 启动了

我们默认是在 可重复读的隔离级别 下说哈:

  1. 在你的事务 400 启动的时候,就会创建一个当前数据的快照,里面记录着数据版本链条,在事务 400 看来,最新版虽然是 'C',但改它的事务 300 还没干完活,不能用;
  2. 再往前看 'B',事务 200 也没干完,也不能用;
  3. 最后你顺着指针找到了最原始的 'A',这个能用;

哪怕在你事务 400 读的过程中,那两个事务都提交了,由于使用的可重复读级别,事务 400 后面每次都会读取一开始的那个快照记录(记录的只有 'A' 可用),所以后续拿到的结果和第一次的结果一样,依然是 'A',这就是实现了“可重复读”。

动画演示

下面的动画演示了这个过程,你可以选择在不同隔离级别下“事务 400” 看到的数据版本链情况,默认是 可重复读的隔离级别

MySQL MVCC 演示

事务隔离级别下的版本链可见性演示

MySQL MVCC

MVCC 原理与事务隔离级别演示

可切换隔离级别,查看各情况下的 ReadView 状态、版本链可见性。

推荐点击右边的“全屏查看”。

事务隔离级别

事务 400(读取者)

执行 SELECT,一致性读取当前可见版本。

表中最新版本

Clustered Row

VALUE"C"DB_TRX_ID300DB_ROLL_PTR-> 200STATUSActive

Undo Log 版本 1

历史快照

VALUE"B"DB_TRX_ID200DB_ROLL_PTR-> 100STATUSActive

Undo Log 版本 2

初始已提交

VALUE"A"DB_TRX_ID100DB_ROLL_PTR-> nullSTATUSCommitted
尚未读取