InfoQ 推荐 ( ) • 2022-08-04 18:17

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

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

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

1.1.当前读的基本概念

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

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

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

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

1.开启一个事务然后查询xscjb中的数据,看到小明的ywcj是100 mysql> 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隔离级别下,首次快照读读的版本数据,在后续的快照读中也会复用该数据,做到重复读。