MVCC是面试的重点,所以这个有必要花些时间了解,全文看完大概需要15-20分钟,希望本文能对你有所帮助。(文章为原创,没有去copy人家的)当然没有了解事务的基本隔离级别还是不要继续看了,还是要懂得MVCC的来源才可以读下去滴。
前备知识:
1.MVCC涉及到的隐藏列,trx_id,roll_pointer
- trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列。
- roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
(可能有小伙伴不知道聚簇索引是什么东西,其实聚簇东西就是索引和数据放在了一块,比如说我们的InnoDB引擎,而非聚簇索引比如说MyISAM,这个就将索引和数据分开了。)
简单来说,trx_id就是每个事务对一个记录进行修改时,都会把它的id写到这个隐藏字段里,而回滚指针则是会指向undo日志,undo日志是用来保证事务的原子性,可以让事务回滚。
可以看到白色的就是三个隐藏列,row_id是因为可以被省略掉,只有在没有主键或者唯一约束列的时候才会被添加该字段。所以可以看到图上画了虚线框,前面的额外信息记录也是如此的处理,虚线框代表不一定需要。
2.快照——ReadView
这里也要记住四个字段,加上前面两个隐藏列,也就是六个小东西需要理解并记住。
- m_ids :表示在生成ReadView时活跃的事务id列表。
- min_trx_id :活跃事务id列表对应的最小值
- max_trx_id :全局最大事务id+1。(全局事务是什么呢,系统分配事务id是递增的,生成ReadView的时候系统应该分配给下一个事务的id,如果全局现在只有三个事务,则全局最大事务id为3,max_trx_id为4)
- creator_trx_id :表示生成该ReadView的事务的事务id。
注意:只有在对表中的记录做改动时,才会为事务真正分配一个id,你一个只读事务不会给你分配id,也不是不会,分配个0。只是个小提示而已。
(多说几句吧,如果你要测试MVCC的话,假如事务100对一个表中的数据进行了更新,这个时候会分配事务id,如下图,再来一个事务102来进行测试的话,先对其他表做一些更新/插入/删除的删除,是为了让它分配一个事务id,才来和事务100进行MVCC的测试,如果看不懂就跳了吧)
讲完了预备知识,就来到了正菜,为了方便理解,后文贴了人家的图来辅助理解。
MVCC也叫多版本并发控制,为什么叫多版本呢,上面的图片很好的诠释了一个版本链的东西,并发控制指的是如何控制并发事务来避免出现一些脏写、脏读、不可重复读、幻读的问题。MVCC主要是用来实现可重复读和读已提交两个隔离级别,下面就针对这两种情况看看MVCC是怎么实现的。 要判断某个记录版本是否对某个事务有以下规则:
- 如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
- 如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的 trx_id 属性值大于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
- 如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下
- trx_id 属性值是不是在活跃事务id列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;
- 如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
读已提交
1.假如事务id为80的事务插入了一条记录。
2.事务id为100的事务进行了修改
目前的版本链:
3.此时有一个使用读已提交的事务开启了一个快照,其实也就是一个select语句,此时分析四个关键字段与隐藏列的关系,活跃事务id列表为[100,200],min_trx_id为100,max_trx_id为201,creator_trx_id为0,前文说过只是查询,并没有对表记录做实际的修改,所以并不会实质赋予一个事务id,赋予事务id为0。
目前页面的记录——生成该版本的事务id为100,所以对快启快照的事务来说不可读,根据回滚指针跳到下一个版本也不行,再到下一个版本,id为80,小于min_trx_id,对快照事务可见,说明生成刘备这条记录在创建快照的事务之前就已经提交了,事实也是如此。这也就是读已提交的验证,事务100此时还没提交对于快启快照的事务是不可见的。下面进一步验证:
此时提交事务100,事务200更新表的记录如下。
目前的版本链:
这时再开启一个快照select,称为select2,同理分析重要的四个字段:
活跃事务id列表只剩200,min也为200,max为201,create为0,这里简写了
页面的目前记录——生成该版本的事务id为200,不符合一直跳到张飞对应的版本才可见。分析结束。 所以这两次select都会开启一个新的快照ReadView。如果事务id200的记录也提交,此时select3的话诸葛亮就可见了。
可重复读
对于可重复读的话,它是在第一次读取数据时生成一个ReadView,事务没提交之前你再select读取数据用的还是第一次的快照。首先,还是事务id为100和200的两个事务,事务id插入了两条数据但是还未提交,如下图。
然后假设现在有一个可重复读隔离级别的事务开始执行了,执行了一个快照读select。读者可以猜一下会读到哪条记录,应该是刘备是吧。此时活跃事务id为100和200,然后两条trx_id为100的值是读不到的。之后我们提交事务100,然后在事务200中进行表的更新,如下图。
此时再执行一次快照读:首先,还是回用到上次的ReadView,所以四个重要字段没有改变,活跃事务id列表依旧为100,200,min_trx_id为101,max_trx_id为201,creator_tex_id为0,然后trx_id为200的记录肯定不可见,100的两条记录呢,也在活跃事务id列表里,所以也不可见,直到跳到最后一条记录刘备才可见。
所以我们两次进行select的结果都是一样的,都是刘备,即便说我们把事务Id为200的事务也提交了,也会是刘备,因为我们一直用的是之前的那个快照读,所以在快照读期间其他事务提交与否是不会影响到我们的结果的。而像读已提交是在每次执行select的时候都会生成一个新的ReadView,事务id为100的记录提交之后,再次进行select的话,它的活跃事务id列表就只剩200了,所以100的记录对它是可见的。
总结
1.对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同:
- 「读提交」隔离级别是在每个 select 都会生成一个新的 Read View,也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
- 「可重复读」隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View,这样就保证了在事务期间读到的数据都是事务启动前的记录。
2.MVCC适用于快照读,也就是普通select语句,当前度不行,select ... for update 等语句。
3.MVCC在访问记录的版本链的过程,可以使不同的读-写,写-读操作并发执行,提高系统性能。
情景模拟:
面试官:谈一下你对MVCC的理解
:MVCC是多版本并发控制,主要用于读已提交、可重复读两种隔离事务在执行快照读的时候访问记录的版本链的过程,可以使不同事务的读写操作并发执行,提高系统性能。在两个不同的隔离级别最大的不同就是生成的时机不同。具体怎么实现并发控制的话,主要通过ReadView和两个隐藏列来实现....balabala