说到mvcc我们就要先说一下它的原理undolog版本链和ReadView机制

undolog版本链:undolog其实就是事务回滚

我们大家都知道,当事务执行失败的时候,我们就要执行回滚,那么如何执行回滚呢,就要靠undolog,所记录的事务执行之前该行数据所对应的上一个版本的数据。

undolog的结构:row_trx_id (记录该行数据所对对应的更新数据的事务id)和 roll_pointer(记录该行数据的上一份row_trx_id),两个字段中间所夹带的就是该行数据

undolog版本链就是undolog的pointer指向trx_id所形成的链

mysql mvcc怎么实现当前读_mysql mvcc怎么实现当前读

ReadView机制:决定,事务该读取哪一部分的数据

在事务形成的时候会为事务创建四个字段:

creator_trx_id: 当前事务的 id; 

m_ids: 当前系统中所有的活跃事务的 id,活跃事务指的是当前系统中开启了事务,但是还没有提交的事务;

min_trx_id: 当前系统中,所有活跃事务中事务 id 最小的那个事务,也就是 m_id 数组中最小的事务 id;

max_trx_id: 当前系统中事务的 id 值最大的那个事务 id 值再加 1,也就是系统中下一个要生成的事务 id。

我们说一下事务读取的规则:

数据的trx id若小于min id则可以读取:说明是在事务开启之前提交的数据

大于或等于max则不可以读取因为是在事务开启后新事物修改的数据

若处于范围之内又有两种结果:如果在ids数组内则不能读取,因为是同时开启事务的修改的数据,如果不处于则可以读取(为什么会不在ids数组的原因是因为隔离级别的原因,在读提交的隔离级别下,是可以读取到数据的)

mvcc机制解决不可重复读的问题

举例:事务a id=1和事务b id=2同时建立并拥有readview的字段,a读取数据(版本1)事务b修改数据并提交事务,此时数据版本为2,因为b提交事务所以b从系统的ids活跃数组中消失,a再读取事务,此时最新版本是b的版本2,查询一看,版本2在readview数组中为同时开启的事务提交的数据,所以不能读取,查询pointer上一个版本数据为1,就可以读取,解决了不可重复读的问题

事务a再次读取数据的时候其实还会判断id 2是否在ids活跃数组中,但是因为在可重复读的隔离级别下,事务a的readview在事务创建后就不会再次更新,所以即使事务b已经提及了,事务b仍然在事务a的readview的ids活跃数组存在,因为它不更新。但是系统的活跃数组其实事务b已经不存在了

MVCC解决幻读:幻读就是读取到原来不存在的数据(insert的数据)

我们读取事务的时候都是基于快照读,那么什么是快照读呢?也就是我们上文所提到的readview,那么如果我们想读取到当前最新数据呢(也就是当前读)那么就是为sql语句加锁(共享锁(share in mode)或者排他锁(for update))

新的事务插入数据会有事务trx_id记录,旧事务读取数据的时候id不在min到max之间,所以不会读取到数据的

注意条件是在可重复读的隔离条件下

重点:读提交是如何运行mvcc机制

这里的最重要的一个不同点就是readview机制的改变:在不可重复读的情况下,每次读取数据readview是不会更新的,也就是,事务创建的时候什么就是什么,而在读提交的隔离级别下,事务每次读取数据readview都会更新也就是事务所携带的ids活跃数组会更新

如何避免脏读问题

当事务a与事务b同时开启的时候,事务a读取数据,事务b修改数据但是未提交事务,事务a再次读取数据,这里重点:此时,事务a再次更新readview,但是事务b未提交所以并未有什么变化。

事务b未提交但是undolog已经形成数据最新的版本是b的id,事务a读取数据的时候,规则:最新版本id处于min到max之间并且事务b存活于ids数组,所以要向上查询,避免了脏读

如何读取到已经提交到的数据

同,上一个实例:事务b修改了数据但是提交了事务,所以事务b会在系统的ids数组中消失,事务a再次读取数据,更新readview(事务b从ids活跃数组中消失),所以读取判断:最新数据id在min到max之间但是id不在ids活跃数组中,故可以读取(这也就解释了上文中,为什么存在于min到max之间而却不在ids活跃数组中的问题)。