浅谈MVCC机制

概念

MVCC(Multi-VersionConcurrencyControl)(注:与MVCC相对的,是基于锁的并发控制,Lock-BasedConcurrencyControl)是一种基于多版本的并发控制协议,只有在InnoDB引擎下存在。

MVCC为了实现事务的隔离性,通过版本号,避免同一数据在不同事务间的竞争,类似基于多版本号的一种乐观锁。当然,这种乐观锁只在事务级别未提交锁和已提交锁时才会生效。

MVCC最大的好处就是:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。

可重复读

事务隔离等级为RR,id=1balance=1000

image-20220227114043787

按时序执行后结果如下:

-事务1:image-20220227114726355
-事务2:image-20220227114726355

MVCC的实现机制

InnoDB在每行数据都增加三个隐藏字段:

1.一个6字节的DB_TRX_ID字段即:通常在其他文章中看到的事务id
2.一个7字节的DB_ROLL_PTR字段即:通常范围以回滚指针
3.一个6字节的DB_ROW_ID字段,该字段在当前事务自增,主要是确定当前数据id(本文章中不涉及该字段)

注意:一般在讲解mvcc的文章中不提及第三个字段,请大家大家鉴别式阅读

MVCC底层原理

事务id

首先说明一下mysql事务id的生成时机,当开启一个事务的时候,并不会立刻生成事务id,而是执行增删改的时候才会生成事务id,事务id累加.

注:

1.我们在前三个事务开始都执行了一个不相干的update,保证事务id生成
2.为了保证文章的可读性,分别把事务id记录为100,200,和300
3.最后两列因为开启事务后,没有执行增删改所以不会有事务id生成

image-20220227110700581

read-view

当我们开启一个select的事务时,mysql底层会在第一次执行select时生成一个read-view,它是由当前所有未提交的事务id+已提交事务id的最大值组成。

例如在上图中①的位置,此时mysql地城会生成一个read-view=[100,200],300,同时在该事务中一旦read-view生成,该事务的read-view将不会改变,除非事务提交。

同理,在上图中④的位置,最后一列事务的read-view=[200],300。

undolog

当我们执行增删改时其实并不是真的把之前的数据删除了,而是记录到了undolog中。

1.insert操作,底层会把记录先写入undolog中,提交时在写入对应的空间
2.delete,底层并不会会把记录直接删除,而是把原来的数据复制一份到undolog中,并记录一个删除字段为true(不属于文章开头说的三个字段)
3.update操作,如果我们对同一条数据进行了多次更新操作,则在undolog中将存在多份,他们之间用DB_ROLL_PTR字段关联。

数据可视性原则

当进行增删改时,原来的数据并不会删除,undolog中存放了很多"待提交数据",那么当查询时底层是怎么决定哪些数据显示,哪些不显示呢?我们来聊一下mysqlmvcc的显示规则

首先,mysql把数据分为三种状态:

注:下图中时间早晚指的是生成read-view的时间或生成事务id的时间;并不是执行begintransaction的时间。

image-20220227112324451

显示数据地读取规则很明确:

1.当前事务开启时已经提交的数据,对当前事务的查询是可以查询到的(灰色部分)
2.比当前事务开启晚的事务提交的数据,对当前事务的查询是不可见的(橘色部分)
3.比当前事务开启早,但是数据在当前事务开启前并未提交,。这部分数据对当前事务不可见
4.事务自己提交的数据,自己可见

在此,对read-view中的事务id做一个概念上的划分:

1.**min_trx_ids:**我们把read-view生成时所有未提交的事务id叫做min_trx_ids(是一个数组),
2.**max_trx_ids:**read-view生成时系统中最大的事务id叫做max_trx_ids
3.creator_trx_id:把当前事务的id叫做creator_trx_id

img

MySQL底层是按照如下的规则判断事务的可见性的:

1.如果被访问版本的trx_id属性值与read-view中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
2.如果被访问版本的trx_id属性值小于read-view中的min_trx_id值,表明生成该版本的事务在当前事务生成read-view前已经提交,所以该版本可以被当前事务访问。
3.如果被访问版本的trx_id属性值大于read-view中的max_trx_id值,表明生成该版本的事务在当前事务生成read-view后才开启,所以该版本不可以被当前事务访问。
4.如果被访问版本的trx_id属性值在read-view的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在min_ids列表中,如果在,说明创建read-view时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建read-view时生成该版本的事务已经被提交,该版本可以被访问。

简单总结

MySQL中MVCC底层通过比对read-view中的事务id来判断undolog中的数据对当前事务是否可见。