MVCC是什么?有什么作用?
MVCC即多版本并发控制,每行数据存在多个事务版本,通过对数据多个版本的访问可以使读操作不会阻塞写操作,写操作不会阻塞读操作。我们所使用的mysql,其默认引擎为inndb,就支持MVCC,使用MVCC来提高mysql读写的性能。
MVCC的实现原理
MVCC通过行隐藏的字段、undo log日志以及Read View 来实现。
行隐藏的字段
每一行数据含有三个字段,分别是row_id、trx_id和roll_pointer。 row_id:行ID,当存在主键和非空唯一键时默认为row_id,如果不存在,则每一行数据隐藏了行ID trx_id:事务id,即数据对应的事务版本号。 roll_pointer:回滚指针,指向undo log日志中的数据
undo log日志
mysql我们直到其存在三大日志,分别时binlog、undo log、redo log。 undo log日志会在事务执行之前记录数据的信息,当事务执行发生错误需要进行回滚时,会通过undo log日志将数据回滚到执行之前的数据。
版本链
当多个事务对数据进行修改时,会通过行隐藏的回滚指针和undo log日志形成一条版本链。版本链从最新数据的回滚指针指向undo log日志中的历史数据版本,然后相连形成一条版本链。
Read View
我们上面介绍了行隐藏的字段和undo log日志,多个事务可以从版本链中读取想要的数据版本信息,那么每个事务是怎么找到它们想要的版本呢?或者换一种说法它们怎么找到当前事务可见的数据版本呢? 通过Read View实现,当事务执行时进行一次快照读也即普通查询时,会生成一个Read View视图,在版本链中查找数据后,会通过Read View内部的算法来判断是否对当前事务可见,如果不可见,会在版本链中继续遍历。 Read View实现属性 :当生成Read View时,会包含以下属性。 m_ids:当前系统活跃(未提交)的事务id集合 min_limit_id:m_ids中最小的事务id,即最小未提交的事务id max_limit_id:将要生成的事务id,由于事务id递增生成,因此将要生成的事务id对当前事务是不可见的 creator_trx_id:当前事务id Read View判断可见性算法: 对于一个事务trx_id,由Read View内部算法判断是否可见,如下: 1、如果trx_id < min_limit_id,由于事务id是递增的,而事务id小于最小未提交的事务,说明该事务已经提交,对当前事务是可见的。 2、如果trx_id >= max_limit_id,说明该事务版本是在当前事务生成Read View视图之后执行的,对当前事务是不可见的。 3、如果min_limit_id <= trx_id < max_limit_id,进行以下的判断: @1:如果m_ids中包含trx_id,并且trx_id !=creator_trx_id,说明该事务是当前事务生成Read View视图时未提交的事务,并且不是当前事务,对当前事务是不可见的。 @2:如果m_ids中包含trx_id,并且trx_id ==creator_trx_id,说明该事务是当前事务生成Read View视图时未提交的事务,但是该事务是当前事务,因此是可见的。 @3:如果m_ids中不包含trx_id,说明该事务已经提交,对当前事务是可见的。
MVCC在RC下避免脏读
当事务101开启后,修改了id=1的数据后会生成一条事务id=101的数据版本 当事务102开始执行时,进行一次快照读,会生成Read View视图,事务101此时还未提交,生成的视图如下: 此时在版本链中先找到了事务id=101的数据,由于m_ids包含101并且101 != 102,所以事务id为101的数据对当前事务不可见。 再次遍历找到事务id=100的数据,由于100 < 101,所以事务id=100的数据对当前事务是可见的,因此避免了脏读的问题
MVCC在RC造成不可重复读、丢失修改
对于上图执行的顺序,由于在RC每进行一次快照读都会生成一个Read View。 第一次查询数据时: 生成视图: 由于100 < 101,对当前事务可见,读取了事务id=100的数据。 当进行第二次查询时: 生成视图: 由于事务102修改数据,生成事务id=102的数据版本。 当遍历事务id=102的数据版本时,由于 101 <= 102 <103并且m_ids中不包含102,因此事务id=102的数据版本对当亲事务可见,读取了事务id=102的数据。 由于事务的多次查询结果不一样,导致了不可重复读的问题。
MVCC在RR下解决不可重复读问题
在RR隔离级别下,每个事务只能生成一个Read View,就可以保证一个事务中进行多次查询时使用的是同一个视图,就保证了查询的数据相同,解决了不可重复读的问题, 同MVCC解决不可重复读问题一样,MVCC解决了幻读的问题,但是并没有完全解决,还存在一些问题。
RR下仍然存在幻读的问题
如上图,事务102插入了id=10的数据,由于Read View事务101本来是对事务102插入的数据是不可见的,但是由于事务101对插入的数据进行了修改,而当前修改的数据对当前事务是可见的,就导致了事务101读取了新插入的数据,造成了脏读的问题,但是在业务中发生这种情况的概率相当低