写点什么

深入理解 MySQL 事务 MVCC 的核心概念以及底层原理

作者:jiangxl
  • 2022 年 8 月 03 日
  • 本文字数:10272 字

    阅读完需:约 34 分钟

深入理解MySQL事务MVCC的核心概念以及底层原理

MVCC 多版本并发控制核心概念以及底层原理

1.当前读与快照读的基本概念

在 MVCC 多版本并发控制中,核心概念和原理是非常复杂的,我们先来搞清楚 MVCC 中常见名称的基本概念,然后再来讲解什么是 MVCC 以及 MVCC 的原理。

1.1.当前读的基本概念

当前读指的是在事务中,通过 Select 查询语句读取的数据记录是当前表中最新版本的记录,默认情况下,在事务中读取表中的数据时,为了避免并发事务对我们读取的数据进行修改,会对读取的记录加锁,即使其他事务修改了表中的数据,我们读取到的数据仍然是其他事务修改之前的数据。


即使在事务中,我们也想要读取当前表中最新的数据记录,而并不是进入事务时查询到的数据,那么此时就需要用到当前读的概念,突破事务一开始读取数据的锁,通过当前读来读取表中最新版本的数据记录。


如何才能突破读取表记录加的锁呢?很简单只要触发当前读的机制,使当前的查询语句进化成当前读的行为,就可以读到表中最新版本的数据,当事务中执行的 SQL,如select lock in share mode、update、insert、delete、select...for update这些,产生了共享锁和排它锁,此时就会产生当前读。


下面来演示一下当前读的效果。


1.开启一个事务然后查询xscjb中的数据,看到小明的ywcj是100mysql> begin;mysql> select * from xscjb;+----+--------+------+------+------+------+| xh | xm     | ywcj | sxcj | yycj | pjcj |+----+--------+------+------+------+------+|  1 | 小明   |  100 |   75 |   93 | NULL |
2.此时再开启一个事务修改小明的ywcj并提交mysql> begin;mysql> update xscjb set ywcj = '999' where xm = '小明';mysql> commit;
3.此时第一个事务任然读到的数据是当前事务进入时的数据状态,并非是最新的数据
4.如果想要读取表中最新的数据,那么就需要通过产生共享锁、排查锁的方式读取到mysql> select * from xscjb lock in share mode;+----+--------+------+------+------+------+| xh | xm | ywcj | sxcj | yycj | pjcj |+----+--------+------+------+------+------+| 1 | 小明 | 999 | 75 | 93 | NULL |
复制代码


1.2.快照读的基本概念

快照读指的是:开启事务后第一次查询数据的结果集,这个结果集就会被做成快照读,只要还是在当前的事务中,即使数据被其他事务修改了,我们无论执行多少次查询,依旧查询到的是快照读的数据。


如 1.1 中的所示,即使 ywcj 被其他事务修改了,在当前事务中读到的仍然是旧数据,也就是快照读的数据。


简单的 select 产生的都是快照读,快照读取的是记录数据的可见版本,也有可能是历史数据,不加锁是非阻塞读。


在不同隔离级别下,快照读也不同:


  • Read Committed:每次 select 查询,都会生产新的快照读。

  • Repeatable Read:开启事务后第一个 select 查询,就是快照读的地方。

  • Serializable:快照读退化成当前读。


快照读也保证了数据的可重复读。

2.什么是 MVCC 多版本并发控制

MVCC 全称是 Multi-Version Concurrency Control,多版本并发控制,MVCC 可以维护一个数据的多个版本,使读写操作没有冲突。


MVCC 是一种并发控制的方法,有了 MVCC 的支持后,不再使用单纯的行级锁对数据库中的并发进行控制,而是使用 MVCC 将数据库中的行锁与行的多个版本进行结合,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能。


并发控制也很好理解,有人通过事务读取了表中的数据,同时也有人通过事务在表中写入或修改了数据,就会导致多个人看到的数据是不一致的,通过并发控制的手段,使每个连接者,在某个瞬间看到的数据时一个快照,即使通过其他事务修改了表中的数据,对于读者来说也是看不到的,从而保证数据的一致性。


MVCC 实现的是读写不阻塞,读写互不影响,通俗一点来说,MVCC 可以使用户觉得数据库对于同一条数据,面对多个事务并发情况下,有多个不同版本的数据所提供。


MVCC 多版本并发控制通过一定的机制生成一个数据请求时间点内,一致性的数据内容,也就是快照,并且利用这个快照来提供一定级别的一致性读取,使用户读写数据互不影响。


MVCC 实现原理依靠于三个部分:隐式字段、undo log 日志、ReadView。

3.MVCC 多版本并发控制依赖的三个组件重要概念

我们知道什么是 MVCC 之后,接下来就需要去探讨 MVCC 多版本并发控制实现的原理了,再研究原理之前,先弄明白,MVCC 依赖的隐式字段、undo log 日志、ReadView 是什么东西。

3.1.MySQL 表中三个隐式字段的概念

当我们创建好一张数据表之后,除了表中所有的字段外,InnoDB 存储引擎还会添加上三个隐藏的字段。


  • DB_TRX_ID

  • 表中的数据时会被修改的,INSERT、UPDATE、DELETE 这些语句默认情况下,一条就代表一个事务,这个字段就是来记录最后一次修改本条数据的事务 ID。

  • DB_ROLL_PTR

  • 该字段是指针,代表回滚指针,该字段值会记录本条数据上次修改前的一个版本,每条数据被修改后都会在 undo log 中进行记录,在 undo log 记录的每条数据中都会有一个版本号,该字段就是来记录本条数据上次修改前在 undo log 中的版本号,可以配合 undo log 日志进行数据的回滚。

  • DB_ROW_ID

  • 该字段可能会出现也可能不出现,主要取决于表中是否存在主键,如果表中没有主键,该字段就会出现,通过自增的方式为每条数据记录一个 ID,主要是为聚集索引服务的。


我们查看一个有主键的表所包含的隐式字段,当表中有主键时,只会出现 DB_TRX_ID 和 DB_TRX_ID 隐式字段。


[root@k8s-master ~]# ibd2sdi /var/lib/mysql/db_1/xscjb.ibd | grep name        "name": "xscjb",                "name": "xh",                "name": "xm",                "name": "ywcj",                "name": "sxcj",                "name": "yycj",                "name": "pjcj",                "name": "DB_TRX_ID",          #记录事务ID的隐式字段                "name": "DB_ROLL_PTR",          #记录回滚指针版本的隐式字段                "name": "PRIMARY",                "name": "idx_xscjb_ywcj",        "name": "db_1/xscjb",                "filename": "./db_1/xscjb.ibd",
复制代码


我们查看一个没有主键的表所包含的隐式字段,当表中没有主键时,三个隐式字段都会出现。


[root@k8s-master ~]# ibd2sdi /var/lib/mysql/db_1/jszx_xgymjzxxb.ibd | grep name        "name": "jszx_xgymjzxxb",                "name": "id",                "name": "bm",                "name": "name",                "name": "xb",                "name": "nl",                "name": "szd",                "name": "zjhm",                "name": "wd",                "name": "first_injection",                "name": "second_injection",                "name": "third_injection",                "name": "wjzymjtyy",                "name": "zhycjzymdsj",                "name": "DB_ROW_ID",        #记录行ID的隐式字段                "name": "DB_TRX_ID",        #记录事务ID的隐式字段                "name": "DB_ROLL_PTR",        #记录回滚指针版本的隐式字段                "name": "PRIMARY",        "name": "db_1/jszx_xgymjzxxb",                "filename": "./db_1/jszx_xgymjzxxb.ibd",
复制代码

3.2.undo log 日志以及版本链的概念

undo log 是回滚日志,当数据库中产生 insert、update、delete 操作时就会产生便于数据回滚的日志,该日志就是 undo log。


undo log 日志是可以被删除的,当产生 insert 语句后,事务一旦提交,undo log 中的 insert 语句就可以被立即删除,因为 undo log 只会在回滚时用到,像 update、delete 语句则不会立即删除,因为还有可能其他事务再读取这些数据。


undo log 日志是实现 MVCC 版本控制最核心的一点,undo log 日志中的版本链为数据形成了一份不同内容版本的链,这些链都会记录在 undo log 日志文件中。


下面我们通过几幅图来演示 undo log 日志中的版本链的概念。


有一张表的原始数据如下,表中只有一条记录,DB_TRX_ID 字段的值为 1,因为是新表只有一条数据,那么对应的事务 ID 也就是 1,DB_ROLL_PTR 字段的值为 NULL,新插入的数据没有被更新过,因此该字段的值为 null。

目前有四个并发事务(事务 1 是插入了这条数据,从事务 2 开始)同时操作这张表中的数据,我们来观察 undo log 会记录什么。

(undo log 中记录的是 sql 语句,这里为了方便演示,以真实数据代替)


1)事务 2:修改表中 id 为 30 的数据,将 age 的值修改为 3,修改完成后提交事务。


如下图所示,当事务 2 中的修改语句执行时,首先将旧数据记录在 undo log 日志中,然后再去更新表中的记录,并且更新表中的数据时,会将隐式字段 DB_TRX_ID 的值更新成事务 2 的 ID,同时回滚指针字段 DB_ROLL_PTR 的值也会指向 undo log 中记录的旧数据对应的版本号,用于将来回滚使用。



2)事务 3:修改表中 id 为 30 的数据,将 name 字段修改为 A3,修改完成后提交事务。


首先也是将变更前的数据记录到 undo log 日志文件中,此时的版本链不变,记录好之后,开始修改表中的数据,同时会将本条数据的 DB_TRX_ID 字段值修改成最后一次事务的 ID,DB_ROLL_PTR 的值会修改成 undo log 中记录的修改前的旧数据对应的版本号,此时链就发生了改变了,表中数据的 DB_ROLL_PTR 值指向了最新一次旧数据的版本号,那么 undo log 中最新一次旧数据表的版本号同样也会指向它上一次旧数据对应的版本号。


新数据指向最新一次旧数据的版本号,最新一次旧数据指向上次旧数据的版本号。



3)事务 4:修改表中 id 为 30 的数据,将 age 字段修改为 10,修改完成后提交事务。


此时版本链的编号和事务 3 基本一样了,首先在 undo log 中记录旧数据,然后修改新数据的内容,然后将 DB_TRX_ID 字段修改成最新事务的 ID,将 DB_ROLL_PTR 指针指向上一次旧数据对应的版本号,undo log 中的最新一次的旧数据,也会指向上一次旧数据对应的版本号。



最终我们可以看到在 undo log 中已经形成版本链了,不同事务或者相同事务操作一条记录时,会在 undo log 中为这条记录生产版本链表,链表的头部是最新的旧数据记录,链表的底部是最早的旧数据记录。

此刻我们的一条数据就对应了很多个不同版本的数据情况,那么如何来识别读哪一个版本的数据呢?就需要去了解 ReadView 了。

3.3.ReadView 读视图的概念

ReadView 读视图:是 SQL 产生了快照读时,生成一个 ReadView 作为 MVCC 读取数据的依据,我们知道当产生快照读时,读到的几乎都是历史数据,并不是最新数据,在 undo log 中记录的每一条旧数据记录都是历史数据,一条数据可能会对应很多个版本的历史数据,那么快照读在执行时究竟应该读取哪一个版本的数据呢?其实就是靠 ReadView 读视图来决定的。


在 ReadView 中有四个字段来记录不同类型事务的 ID,并且这四个字段与 undo log 中的事务 ID 有相应的匹配规则,当满足某一项规则时,就会读取该规则对应的版本的历史数据。


ReadView 读事务会依据以下四个字段判断要读取那一个版本的历史数据:


  • m_ids:是一个集合,当前活跃的所有读写事务的事务 ID 都会记录在这个 ids 集合中。

  • 活跃的事务就表示当前事务正在进行中,还没有提交,只要事务没有提交都会处于活跃的状态。

  • min_trx_id:最小活跃事务 ID。

  • 是基于 m_ids 集合内的所有活跃的事务 ID,在这个集合中最小的活跃事务 ID。

  • max_trx_id:预分配事务 ID,不是当前活跃的事务集合中最大的事务 ID,相当于一个预留的事务 ID,一般都是当前事务 ID+1 的新事务 ID。

  • creator_trx_id:ReadView 创建者的事务 ID,通常情况也是当前事务的事务 ID。


当前数据中 DB_TRX_ID 字段对应的事务 ID,如果比最小的活跃事务 ID 还要小,就表示 DB_TRX_ID 字段对应的事务 ID 是处于提交的状态。如果 DB_TRX_ID 字段对应的事务 ID 比最小活跃事务 ID 还大,那么说明改事务可能也是活跃事务处于未提交的状态。


有了读取数据版本的依据之后,就需要有对应的规则来决定要读取数据的哪个版本。


ReadView 决定要读取数据的哪个历史版本时,是由 undo log 版本链中数据对应的事务 ID 与 ReadView 的四个字段中记录的事务 ID 进行规则匹配,共有四种匹配规则,当匹配的结果满足规则时,就会读取规则对应的历史版本数据。


undo log 版本链中针对相同的数据可能会记录很多条不同版本的数据,此时就会先拿表中当前的数据与 ReadView 规则进行匹配,如果满足则读取这个版本的数据,如果不满足则再去 undo log 版本链中从上往下一次匹配每一条数据,当满足 ReadView 规则时,则读取对应版本的数据,


在匹配 ReadView 规则时,是从上往下依次进行规则匹配的,当满足第一个规则时就读取对应的版本数据,后面的规则将不会匹配。读取数据肯定会被某一个规则所匹配。


ReadView 读取某个版本数据时的四种匹配规则:


表中的一条数据或者 undo log 版本链中的一条数据,每一条数据都称为一个版本。匹配规则是先从表中的数据记录开始匹配,如果表中数据不满足规则时,再从 undo log 版本链中对于数据的多个版本,从上到下依次匹配,只要有一个版本满足了规则,则会返回该版本的数据,不会再往下进行匹配。


  • 当 trx_id == creator_trx_id

  • 当某个版本的数据记录中 DB_TRX_ID 字段记录的事务 ID,与 ReadView 的 creator_trx_id 字段记录的事务 ID 相同,那么就可以读取这个版本中的数据。

  • 因为 creator_trx_id 字段的值记录的是当前事务的 ID,如果数据中的记录最后一次操作的事务 ID 值与 creator_trx_id 字段的值相同,就表示是当前事务所修改的数据,因此它是可以读取到最新版本的数据的。

  • 当 trx_id < min_trx_id

  • 当某个版本的数据中记录事务 ID 值,小于所有活跃事务中最小的事务 ID 值时,就可以读取这个版本的数据。

  • 因为所有的活跃事务,不管 ID 是大还是小,都是活跃是未提交的事务,如果当前版本的数据事务 ID 与活跃的事务 ID 相等或者比它 ID 大,就说明这个版本的数据还有可能被其他的事务处理中,因此是不可以被访问到的。只有当版本中数据的事务 ID 比所有活跃事务中最小的那个事务 ID 还要小,就表示该版本的数据没有被事务使用了,已经是提交状态了,因此就可以读取这个版本的数据。

  • 当 trx_id > max_trx_id

  • 当某个版本的数据中记录事务 ID 值,大于预留的事务 ID,那么就不可以读取这个版本中的数据。

  • 如果当前版本中数据记录的事务 ID 比预分配的事务 ID 要大,那么就说明这个版本的数据是在我们当前事务之后又开启的新事务,数据在处理中,因此不可以读取这个版本中的数据。

  • 当 min_trx_id <= trx_id <= max_trx_id

  • 当某个版本的数据记录中事务 ID 的值,大于最小活跃的事务 ID 值,也小于预留的事务 ID 值,也就是事务 ID 位于最小活跃事务 ID 和预留事务 ID 之间的 ID,并且当前版本数据记录中的事务 ID 值不在 m_ids 集合中,当满足这个规则时,那么就可以读取这个版本中的数据。

  • 这个规则相当于给了一个事务 ID 的范围,最小事务 ID---最大事务 ID 区间的事务 ID,如果版本中数据的事务 ID 在这个范围列表里,还需要看一下这个事务 ID 是不是活跃的事务 ID,主要是在 m_ids 集合中查看,如果也不会活跃的 ID,那么就允许读取此版本的数据,否则将不允许。


在四种规则里,trx_id 表示的是当前版本中数据对应的事务 ID 的值,要通过 trx_id 的值和 ReadView 四个字段对应的值,进行匹配,满足相应规则时放行。


在不同的隔离级别下,生成 ReadView 的时机不同:


  • READ COMMITTED :在事务中每一次执行快照读时都生成一个 ReadView,每个 ReadView 中四个字段的值都是不同的。

  • REPEATABLE READ:仅在事务中第一次执行快照读时生成 ReadView,后续复用该 ReadView。

4.MVCC 实现多版本并发控制的原理

MVCC 多版本并发控制实现的原理就是通过 InnoDB 表的隐藏字段、undo log 版本链、ReadView 读视图配合来实现。


1)首先在表中的数据都会有两个主要的隐藏字段,一个是 DB_TRX_ID 记录最后一次操作的事务 ID,还有一个是 DB_ROLL_PTR 记录版本指针用于回滚时使用。


2)在多并发事务的场景下,不同事务操作完表数据会都会将旧数据记录在 undo log,一条数据在 undo log 日志中可能会多个不同版本的数据,最终形成版本链表,不同版本的数据通过 DB_ROLL_PTR 字段值相互关联。


3)当数据在 undo log 日志中形成版本链之后,MVCC 就会通过 ReadView 读视图根据四个字段,与 undo log 版本链中不同版本中数据的事务 ID 进行规则匹配,当 undo log 版本链中从上到下的某一个版本数据满足 ReadView 读视图中的规则,那么就读取该版本对应的数据,并且不会再使用版本链中其他的版本数据再进行规则匹配。


简单来说,MVCC 实现多版本并发控制的原理,就是根据多事务在 undo log 中产生的多条旧数据形成的版本链表,将一条数据的多个版本中的事务 ID 与 ReadView 读视图中的四个字段所对应的事务 ID 进行规则匹配,如果这个版本的数据满足 ReadView 四个字段的规则,那么就读取这个版本的数据,如果不满足规则,则用另一个版本的数据依次进行匹配,知道读到满足规则的数据。


多版本并发控制就是在多事务的场景下,读取针对当前事务最合适的一个版本的数据,可能是新数据也可能是旧数据。


MVCC+锁就实现了事务的隔离性,事务的一致性由 ReadLog 和 UndoLog 保证。

5.不同隔离级别下 MVCC 实现并发控制的原理

5.1.RC 隔离级别下 MVCC 多版本并发控制的原理分析

在 RC 隔离级别下,每当执行的 SQL 是快照读类型的,就会生成一个 ReadView 读视图,每次生成的 ReadView 读视图所对应的四个字段值都是不同的,在 RC 隔离级别下,每次快照读读取的版本数据可能也不相同。


下面我们通过一组事务来分析 RC 隔离级别下 MVCC 多版本并发控制的原理。


如下图所示,在并发事务 5 中,查询了两次 id 为 30 的数据,由于当前的隔离级别是 RC,所以每当产生一次快照读都会生成一个 ReadView,每次生成的 ReadView 四个字段值都不同,也就意味着两次查询相同数据的结果可能都不相同。两次快照读在获取数据时,会根据所生成的 ReadView 四个字段的值与 undolog 版本链中的数据进行规则匹配,最终返回此次快照读的数据。



ReadView 四个字段的获取的值:m_ids 记录所有活跃事务的 id 号,分别是 3/4/5 对应事务 3-事务 5,min_trx_id 记录所有活跃事务中事务 ID 最小的值,那么也就是 3,max_trx_id 是预留的事务 ID,当前事务 ID 是 5,那么预留的事务 ID 就是 6,creator_trx_id 是生成 readview 的事务 id,也就是 5。


1)分析事务 5 中第一次快照读的 MVCC 多版本并发控制的原理流程


如下图所示,左侧是快照读可能会读取的各个版本的数据,有表中的记录,有 undo log 版本链中的记录,右侧是 ReadView 读取版本数据的规则,并且将第一个快照读产生的 ReadView 四个字段的值,带入到了规则中,下面开始匹配。



A)在匹配合适的版本数据时,首先匹配表中的记录:



也就是这条数据,这条数据对应的 trx_id 事务 id 是 4,此时 MVCC 就会通过 ReadView 带着这条数据去 ReadView 规则中进行匹配,在第一条规则中,trx_id 为 4 不等于 creator_trx_id(ID 为 5)的 ID 值,第二条规则中,trx_id=4 大于了 min_trx_id(ID 为 3),第三条规则中 trx_id=4 小于了 max_trx_id(ID 为 6),第四条规则,trx_id=4 位于 min_trx_id(ID 为 3 与 max_trx_id(ID 为 6)之间,但是该版本的数据事务 ID 是 4,4 位于 m_ids(ID:3,4,5)集合中。


规则匹配结果为:1)不满足 2)不满足 3)不满足 4)不满足,该版本的数据都不满足规则,此时就要去 undo log 版本链中匹配下一条数据了。


表中数据不满足了,此时从 undo log 链中从上往下挨个匹配每个版本的数据,当某一个版本数据满足规则后,下面的数据不再进行匹配。


B)然后匹配 undo log 版本链中最上面的数据:



,该版本数据的 trx_id 事务 ID 为 3,将 trx_id=3 带入右侧的 ReadView 版本链中进行匹配,在第一条规则中,trx_id 为 3 不等于 creator_trx_id(ID 为 5)的 ID 值,第二条规则中,trx_id=3 等于了 min_trx_id(ID 为 3),第三条规则中 trx_id=3 小于了 max_trx_id(ID 为 6),第四条规则,trx_id=3 位于 min_trx_id(ID 为 3)与 max_trx_id(ID 为 6)之间,但是该版本的数据事务 ID 是 3,3 位于 m_ids(ID:3,4,5)集合中。


规则匹配结果为:1)不满足 2)不满足 3)不满足 4)不满足,该版本的数据都不满足规则,此时继续从 undo log 版本链中从上到下匹配下一条数据。


C)接着匹配 undo log 版本链中第二条版本数据:



,该版本数据的 trx_id 事务 ID 为 2,将 trx_id=2 带入右侧的 ReadView 版本链中进行匹配,在第一条规则中,trx_id 为 2 不等于 creator_trx_id(ID 为 5)的 ID 值,第二条规则中,trx_id=2 小于 min_trx_id(ID 为 3),该版本的数据满足 ReadView 规则中的第二个规则,此时就会终止匹配,快照读此时就会返回版本链中这个版本所对应的数据。


表中记录、undo log 版本链的数据从上往下依次匹配 ReadView 规则,当有一个版本的数据满足规则后,就返回给快照读获取该版本的数据,这就是 MVCC 多版本并发情况下,分配给快照读合适版本数据的原理和过程。


2)分析事务 5 中第二次快照读的 MVCC 多版本并发控制的原理流程


在第一次快照读时我们已经理解了 MVCC 是如何实现多版本并发控制的,根据表中记录、undo log 版本链中多个不同版本的数据,按照数据中的事务 ID 在 ReadView 规则中进行匹配,当满足规则时,将该版本的数据返回给快照读。


第二次快照度和第一次快照度大差不差,在第二次快照读时,事务 3 提交了,那么在活跃的事务中就没有事务 3 了,数据还是左侧这么多个版本,右侧的规则中为此次生成的 ReadView 四个字段带入了新值。



ReadView 四个字段的获取的值:m_ids 记录所有活跃事务的 id 号,分别是 4/5 对应事务 4-事务 5,min_trx_id 记录所有活跃事务中事务 ID 最小的值,那么也就是 4,max_trx_id 是预留的事务 ID,当前事务 ID 是 5,那么预留的事务 ID 就是 6,creator_trx_id 是生成 readview 的事务 id,也就是 5。


A)首先匹配表中的记录:



也就是这条数据,该版本数据的 trx_id 事务 ID 为 4,将 trx_id=3 带入右侧的 ReadView 版本链中进行匹配,在第一条规则中,trx_id 为 4 不等于 creator_trx_id(ID 为 5)的 ID 值,第二条规则中,trx_id=4 等于了 min_trx_id(ID 为 4),第三条规则中 trx_id=4 小于了 max_trx_id(ID 为 6),第四条规则,trx_id=4 位于 min_trx_id(ID 为 4)与 max_trx_id(ID 为 6)之间,但是该版本的数据事务 ID 是 4,4 位于 m_ids(ID:4,5)集合中。


规则匹配结果为:1)不满足 2)不满足 3)不满足 4)不满足,该版本的数据都不满足规则,此时就要去 undo log 版本链中匹配下一条数据了。


B)然后匹配 undo log 版本链中最上面的数据:



,该版本数据的 trx_id 事务 ID 为 3,将 trx_id=3 带入右侧的 ReadView 版本链中进行匹配,在第一条规则中,trx_id 为 3 不等于 creator_trx_id(ID 为 5)的 ID 值,第二条规则中,trx_id=3 小于 min_trx_id(ID 为 4),该版本的数据满足 ReadView 规则中的第二个规则,此时就会终止匹配,快照读此时就会返回版本链中这个版本所对应的数据。

5.2.RR 隔离级别下 MVCC 多版本并发控制的原理分析

在 RR 隔离级别下,只会在事务第一次执行快照读时会生成一个 ReadView 读视图,后续快照读都会复用这个 ReadView,读取的版本数据都是相同的,也就说明了 RR 隔离级别是可重复度。


下面我们通过一组事务来分析 RR 隔离级别下 MVCC 多版本并发控制的原理。


如下图所示,在并发事务 5 中,查询了两次 id 为 30 的数据,由于当前的隔离级别是 RR,所以当第一次产生快照读会生成一个 ReadView 决定四个字段的值,后面再有快照读执行时,就会复用第一次快照读产生的 ReadView,也就意味着每次快照度产生的结构都是一样的。



ReadView 四个字段的获取的值:m_ids 记录所有活跃事务的 id 号,分别是 3/4/5 对应事务 3-事务 5,min_trx_id 记录所有活跃事务中事务 ID 最小的值,那么也就是 3,max_trx_id 是预留的事务 ID,当前事务 ID 是 5,那么预留的事务 ID 就是 6,creator_trx_id 是生成 readview 的事务 id,也就是 5。


多个版本的数据在 RR 隔离级别下的规则匹配流程与 RC 隔离级别一致。


1)分析事务 5 中首次快照读的 MVCC 多版本并发控制的原理流程


如下图所示,左侧是快照读可能会读取的各个版本的数据,有表中的记录,有 undo log 版本链中的记录,右侧是 ReadView 读取版本数据的规则,并且将第一个快照读产生的 ReadView 四个字段的值,带入到了规则中,下面开始匹配。



A)首先匹配表中的记录:



也就是这条数据,这条数据对应的 trx_id 事务 id 是 4,此时 MVCC 就会通过 ReadView 带着这条数据去 ReadView 规则中进行匹配,在第一条规则中,trx_id 为 4 不等于 creator_trx_id(ID 为 5)的 ID 值,第二条规则中,trx_id=4 大于了 min_trx_id(ID 为 3),第三条规则中 trx_id=4 小于了 max_trx_id(ID 为 6),第四条规则,trx_id=4 位于 min_trx_id(ID 为 3)与 max_trx_idD 为 6)之间,但是该版本的数据事务 ID 是 4,4 位于 m_ids(ID:3,4,5)集合中。


规则匹配结果为:1)不满足 2)不满足 3)不满足 4)不满足,该版本的数据都不满足规则,此时就要去 undo log 版本链中匹配下一条数据了。


B)然后匹配 undo log 版本链中最上面的数据:



,该版本数据的 trx_id 事务 ID 为 3,将 trx_id=3 带入右侧的 ReadView 版本链中进行匹配,在第一条规则中,trx_id 为 3 不等于 creator_trx_id(ID 为 5)的 ID 值,第二条规则中,trx_id=3 等于了 min_trx_id(ID 为 3),第三条规则中 trx_id=3 小于了 max_trx_id(ID 为 6),第四条规则,trx_id=3 位于 min_trx_id(ID 为 3)与 max_trx_id(ID 为 6)之间,但是该版本的数据事务 ID 是 3,3 位于 m_ids(ID:3,4,5)集合中。


规则匹配结果为:1)不满足 2)不满足 3)不满足 4)不满足,该版本的数据都不满足规则,此时继续从 undo log 版本链中从上到下匹配下一条数据。


C)接着匹配 undo log 版本链中第二条版本数据:



,该版本数据的 trx_id 事务 ID 为 2,将 trx_id=2 带入右侧的 ReadView 版本链中进行匹配,在第一条规则中,trx_id 为 2 不等于 creator_trx_id(ID 为 5)的 ID 值,第二条规则中,trx_id=2 小于 min_trx_id(ID 为 3),该版本的数据满足 ReadView 规则中的第二个规则,此时就会终止匹配,快照读此时就会返回版本链中这个版本所对应的数据。


在 RR 隔离级别下,首次快照读读的版本数据,在后续的快照读中也会复用该数据,做到重复读。

发布于: 2022 年 08 月 03 日阅读数: 81
用户头像

jiangxl

关注

CSDN、阿里云、华为云、51CTO等博客专家 2022.04.27 加入

CSDN博客专家、51CTO专家博主、阿里云博客专家、华为云享专家、DevOps运维领域优质创作者,擅长Linux系统运维、开源监控软件维护、Kubernetes容器技术、CI/CD持续集成、自动化运维、大规模互联网WEB集群架构

评论

发布
暂无评论
深入理解MySQL事务MVCC的核心概念以及底层原理_MySQL_jiangxl_InfoQ写作社区