MVCC多版本并发控制

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MVCC多版本并发控制相关的知识,希望对你有一定的参考价值。

MVCC

参见这篇文章:https://www.jianshu.com/p/8845ddca3b23
文章写的非常详细,而且易懂,赞!

来看详细MVCC的直接原因是因为突然想不明白是怎么解决不可重复读的问题?所以来详细研究一下:

MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的。

隐藏字段三个:DB_TRX_ID:表示最近修改/插入事务ID;DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本;DB_ROW_ID:自增主键
还有一个删除标记,即记录被更新或删除并不代表真正的删除了,而是删除标记flag变了

undo日志
undo log主要分为两种:
insert undo log
代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

Read View?
什么是Read View,说白了Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
读视图主要是用来做可见性判断的,即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本

这里read view三个属性:
trx_list:维护read_view生成时刻,系统正活跃的事务ID
up_limit_id:记录trx_list中事务ID最小的ID
low_limit_id:read_view生成时刻,系统尚未分配的下一个事务的ID,也就是目前已出现过的事务ID的最大值+1;

然后根据DB_TRX_ID和这三个属性的关系来判断是否能看到当前版本的记录(这里不太好理解):

  • 首先比较DB_TRX_ID和up_limit_id,如果前者小于后者,那么说明read_view生成时,已经提交了DB_TRX_ID这个版本,当前事务是能看到的,所以返回当前记录;如果大于,继续判断:
  • 比较DB_TRX_ID和low_limit_id,如果前者大于等于后者,说明提交的版本是在read_view生成之后提交的,当前事务是看不到的,如果小于,继续判断:
  • 如果DB_TRX_ID在活跃的事务ID trx_list中,那么说明read_view生成时,事务还在执行,DB_TRX_ID是正在执行的事务修改的记录版本,但是这个事务还没有提交,所以当前事务是看不到的;如果不在这个trx_list中,那么说明read_view生成时,当前数据版本是已经提交的,所以当前事务是可以看到的

那么如何解决不可重复读问题的呢?
是快照的时机不同,在读已提交的隔离级别下,每次快照读都会去生成一个快照和Read View,所以可以看到别的事务提交的数据
而在可重复读的隔离级别下,某个事务对某条记录的第一次快照读时创建一个快照及Read View,而后面再读的时候是不会创建新的Read View,所以不会出现不可重复读的问题

但是这样还是不够严谨,因为,如果发生以下这种情况,快照读的还是已经提交的数据
只不过是再读的时候,还是读的相同的数据,也相当于是解决了不可重复读的问题吧

创建版本号?删除版本号?

参见:https://juejin.cn/post/6844903842505555981

有的地方有这样的说法:
MVCC 在每行记录后面都保存着两个隐藏的列,用来存储两个版本号:

  • 创建版本号:创建一行数据时,将当前系统版本号作为创建版本号赋值。
  • 删除版本号:删除一行数据时,将当前系统版本号作为删除版本号赋值。如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了。

我个人感觉这里的删除版本号和刚刚提到的删除标记应该是干的同一件事情

我觉得这里需要注意的是:update = delete(先) + insert(后)

以上是关于MVCC多版本并发控制的主要内容,如果未能解决你的问题,请参考以下文章

MySQL MVCC(多版本并发控制)

多版本并发控制(MVCC)的原理

MySQL-InnoDB-MVCC多版本并发控制

MySQL-InnoDB-MVCC多版本并发控制

MySQL中InnoDB的多版本并发控制(MVCC)

MVCC(多版本并发控制)原理