MySQL中MVCC多版本并发控制实现RC和RR隔离级别

941 阅读4分钟

「这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

ReadView机制是基于undo Log版本链实现的一整套视图机制,在事务执行的时候生成一个Read View,然后根据不同的事务级别来判断哪些版本的数据是可以读取到的,下面了解下最常用的RC、RR级别是如何实现的。

RC(Read Committed)隔离级别基于ReadView机制实现

RC隔离级别,在事务运行期间,只要别的事务修改数据并且提交,那么就可以读取到其它事务修改的数据。 MySQL 实现RC隔离级别最核心的要点在于:当一个事务设置为RC隔离界别的时候,每次发起查询都重新生成一个ReadView。 假设有一行数据,事务id=30,此时有两个事务,事务A(id=40),事务B(id=50),如下图所示:

image.png

此时事务B发生了更新操作,将这条数据值修改为值B,并未提交事务,所以此时的数据trx_id=50,同时生成一条undo log日志。如下图所示:

image.png

这个时候事务A发起查询,会生成ReadView,min_trx_id=40,max_trx_id=60,crearor_trx_id=60,m_ids=(40,50),当事务A查询的时候,此时数据的trx_id =70,是属于事务ID范围之间,说明这条数据是在事务A生成ReadView之前就有这个活跃的事务,并且这个事务修改了数据值,但是此时这个事务B还没有提交,根据事务隔离级别,未提交的事务修改值是不允许查询的,就会顺着undo log 版本链继续往下查找,就会找到原始值,然后它的trx_id =30 是小于当前ReadView里的min_trx_id,说明是事务A生成ReadView之前已经事务提交的数据,可以被查询到。

image.png

此时事务B成功提交,根据RC隔离级别,事务正常提交的数据应该被正常查询到。那么事务A再次查询的时候如何查询到事务B已经提交的数据呢? 在事务A再次查询的时候,再次生成一个ReadView,此时生成的ReadView,数据库内活跃的事务只有事务A,因此min_trx_id=40,max_trx_id=60,creator_trx_id=40,m_id=(40),事务B不在出现在m_ids 活跃的事务列表里。此时事务A再次基本重新生成的ReadView去查询,会发现数据trx_id=50,虽然在min_trx_id和max_trx_id之间,但是不在m_ids里,说明该条数据在本地ReadView之前就已经提交,可以被查询到。

image.png

RC隔离级别的实现,关键点在于每次查询的时候都生成新的ReadView,如果在这次查询之前有事务修改了数据并且提交了,那么在这次查询的ReadView里,那个m_ids列表就不会存在这个已经提交的事务,那么就可以读取到其它事务修改的值。

RR(Repeatable Read)隔离级别基于ReadView机制实现

RR 隔离级别,事务读取一条数据,无论读取多少次,都是同一个值,其它的事务修改数据后并成功提交事务,那么也是读取不到别人修改的值,这样就避免了重复读的问题。 上面RC隔离机制实现的时候每次查询的时候都生成一个ReadView,为的是剔除在生成ReadView同一时间段提交的事务ID,这样保证可以读取到其它事务修改的值,RR隔离机制实现的时候原理,在同一个事务执行查询的时候,ReadView都采用同一个,这样的话,无论其它事务在提交后,都处理当前事务的执行中,这样的话就不会被当前事务读取到。

那么MySQL是如何解决幻读的呢? 假设有两个事务,事务A(id=40)执行查询与,此时查到的数据是一条,事务B(id=50)执行插入语句,查询出来一条数据,此时的undo如下图所示:

image.png

这个时候事务B提交了数据,并且新来了事务C(id=60) 并且插入了一条数据,然后提交事务,此时如下如所示:

image.png 此时事务A再次查询的时候,会发现符合条件的有3条,一条是事务B插入的数据,这条数据在ReadView执行中,不允许被查询到,一条数据是事务C插入的那人条数据,这条数据的txr_id=60,是大于自己ReadView的max_trx_id ,说明是自己发起查询之后,这个事务才启动的,此时的这条数据也是不能查询的,还有条数据是原始值,这条数据的txr_id=30,小于自己ReadView的min_trx_id,是事务启动之前,其它事务已经提交的数据,是允许被查询的,那么事务A只能够查询到一条数据。