(1)前序
    上章说了,基于ReadView机制可以实现RC隔离级别,即每次查询的时候都生成一个ReadView,这样的话,只要这次查询之前有别的事务提交了,那么别的事务更新的数据是可以看到的。
    如果是RR级别呢?RR级别下,这个事务读一条数据,无论读多少次,都是一个值,别的事务修改数据之后哪怕提交了,也是看不到别的事务修改的值的,这样就避免了不可重复读的问题。同时如果别的事务插入了一些新的数据,也是读不到的,这样就可以避免幻读问题。

    (2)如何实现RR级别呢?MySQL的RR级别解决了不可重复读和幻读的问题
    不可重复读
    举例:假设有一条数据事务 id =50的一个事务插入的,同时此时有事务A和事务B同时在运行,事务A的id是60,事务B的id是70,这个时候事务A发起了一个查询,第一次查询就会生成一个ReadView,此时ReadView里的creator_trx_id是60,min_trx_id是60,max_trx_id是71,m_ids是[60,70] ,这个事务A基于这个ReadView查这条数据,会发现这条数据的trx_id为50,是小于ReadView的min_trx_id的,说明它发起查询之前,就有事务插入这条数据提交了,所以此时可以查询到这条原始值。
    61.jpg62.jpg
    接着事务B此时更新了这条数据的值为B,此时会修改trx_id为70,同时生成一个undo log,而且关键是事务B,此时它还提交了,也就是说事务B已经结束。这个时候ReadView的m_ids此时还会是60和70嘛?
    这是必然的,因为ReadView一旦生成了就不会改变,这个时候事务B虽然已经结束,但是事务A的ReadView里,还会有60,70两个事务id,意思是在事务A开启查询的时候,事务B当时是在运行的,接着此时事务A去查询这条数据的值,会发现数据的trx_id是70,70一方面是在ReadView的min_trx_id和max_trx_id的范围区间的,同时还在m_ids列表中。
    63.jpg64.jpg
    说明什么? 说明起码事务A开启查询的时候,id为70的这个事务B还是在运行的,然后由这个事务B更新了这条数据,所以此时事务A是不能查询到事务B更新的这个值的,因此这个时候继续顺着指针往历史版本链条上去找。
    接着事务A顺着指针找到下面一条数据,trx_id为50,是小于ReadView的min_trx_id的,说明在他开启查询之前,就已经提交了这个事务,所以事务A是可以查询到这个值的,此时事务A查到的是原始值,这样就避免了不可重复读,事务A多次读同一条数据,每次读到的都是一样的值,除非是他自己修改了值,否则读到的都是一样的。
    不管别的事务如何修改数据,事务A的ReadView始终是不变的,基于这个ReadView始终看到的值是一样的。

    幻读
    假设事务A先用 select * from x where id >10 来查询,此时可能查到就是一条数据,而且读到的是这条数据的原始值的那个版本,现在事务C插入一条数据,然后提交了,接着事务A再次查询,此时发现符合条件的有2条数据,一条是原始值,一条是事务C插入的那条数据,但是事务C插入的那条数据的trx_id是80,这个80是大于自己的 ReadView的max_trx_id的,说明自己发起查询之后,这个事务才启动的,所以此时这条数据是不能查询的。因此事务A本次查询还是只能查询到原始值一条数据。