什么是MVCC?
使用MVCC有什么好处(解决的问题)
SQL共有4中隔离级别分别为:读未提交,读已提交,可重复读,串行化。他们分别对3种读如下图:
脏读 | 不可重复读 | 幻读 | |
读未提交 | × | × | × |
读已提交 | √ | × | × |
可重复读 | √ | √ | × |
串行化 | √ | √ | √ |
上述表格可以看到,从上至下,隔离级别越往下,性能越低,但是数据并发出现的问题就越小,而到串行化的时候,已经是最慢的性能了。然而MVCC版本并发控制,可以让可重复读也解决幻读问题,大大加强了sql的性能。如下图:
脏读 | 不可重复读 | 幻读 | |
读未提交 | × | × | × |
读已提交 | √ | × | × |
可重复读/串行化 | √ | √ | √ |
可重复读/串行化,采用的是MVCC+Next-key lock 机制。
MVCC可以不采用锁机制,而是通过乐观锁的方式来解决不可重复读和幻读问题!他可以在大多数情况下代替行级锁,降低系统的开销。
MVCC的实现原理
MVCC的实现原理就是:隐藏字段、UNDO LOG字段链、READ VIEW 视图
什么是Read View
在MVCC机制中,多个事务对同一个行记录进行多版本的更新时,会产生多个历史快照,而这些历史快照都保存在UNDO LOG中,如果一个事务想要查询这个行记录,需要读取那个版本的行记录呢?这时候就需要READ View视图,帮我们解决可见性问题。
ReadView就是事务在使用MVCC机制的时候进行快照读的操作,产生读视图。当事务启动时,会产生数据库系统当前的一个快照,InnDB为每个事务构造了一组数组,用来记录并维护系统当前活跃事务的ID。
而在ReadView视图中主要包含了4个重要内容:
1、creator_trx_id:创建这个ReadView的事务Id
2、trx_ids:表示在生成ReadView时,当前事务活跃的ID列表
3、up_limit_id:活跃的事务最小的事务ID
4、low_limit_id:表示系统最大事务ID+1
MVCC操作流程
当我们查询一条记录时,首先会获取事务自己的版本号,也就是自己的事务ID,然后会生成
undo log(历史快照)+ReadView 在ReadView中会通过最小事务的ID和当前的trx_ids进行比对,如果在当前活跃列表中,就继续向下寻找,直到找到小于自己的一条数据。
大白话将太难以理解,我们直接上图
注意:
MVCC对应的3中级别
读已提交:每次查询都会出现一个新的视图
可重复读:在不提交事务的时候,每次查询都是第一次查询的视图
串行化:和可重复读一样
MVCC读已提交的流程
我在网上找的一段截图,可以看一下
使用读已提交的隔离级别: 每查询一次都会生成一个新的视图
现在有两个事务 事务10 和事务20
可以看到事务10更新了一些数据,事务20没有更新数据,此时会生成一个undo log快照保存起来,当我们使用事务读取当前正在被操作的事务时,就会触发ReadView,在ReadView中会有4个关键性数据 creator_trx_id、 trx_ids、 up_limit_id、 low_limit_id 这几个数据在上述已经讲过,所以trx_ids为[10,20] ; up_limit_id为10; low_limit_id为21 。
在undo log的版本链中挑选数据,第一个是王五trx_id为10,发现已经在ReadView中的trx_ids的范围中,表示还未提交的事务,所以不能查询,接着就依次往下推,李四,也在活跃链中,推到张三trx_id为8时,发现不在活跃的版本链中,表示已经提交过了,可以查询该数据,所以就返回张三这条数据!
再来看可重复读的级别:MVCC解决了幻读和不可重复读问题
MVCC可重复读流程
还是看直接看图片,便于理解
我们可以发现,这个得到的值为张三,和上述读已提交的操作一样
这一次查询生成的ReadView为 :trx_ids为[10,20] ; up_limit_id为10; low_limit_id为21
因为当前的隔离级别为可重复读,所以,接下来的事务都会使用这个ReadView视图
我们发现它的undoLog版本链发生了变化,但是他的视图ReadView还是第一次查询的视图,第一次查询的视图中的trx_ids[10,20]
所以,在undolog通过trx_id和ReadView进行对比时,最后还是查询到的张三这条数据!
MVCC幻读的流程
如果上述两个隔离级别理解了,幻读的时候就很好理解了,因为幻读也是在第一次查询的时候生成ReadView视图,所以接下来,不管查询几次,都只会有这一个视图,因此当我们进行插入数据完成后,在进行undolog链和ReadView进行对比时,发现,插入的新数据在活跃id中,所以查询不到当前数据,接下来也是依次往下查找,直到找到不在版本链中的数据!