这里写自定义目录标题
- 为什么要有MVCC?
-
- 如何实现?
- [Read View以及可重复读的隔离原理](#Read View以及可重复读的隔离原理)
- 读已提交是如何实现的?
- 总结
为什么要有MVCC?
从用户需求出发,数据库想要实现什么功能呢?事务想要实现隔离性,也就是如果多个事务同时运行,各个事务之间不能相互打扰(不能有脏读、不可重复读、幻读)。其实加锁可以简单粗暴的解决这个问题,但如果连读操作都频繁加锁,显然会严重影响效率,因此如何在不加锁的情况下解决多个事务的并发读问题,是多版本并发控制(MVCC)实现的出发点。
如何实现?
现在我们有很多个事务,它们面对着同一张表,表里有很多条数据。有的事务可能在修改某条记录,有的在查询,有的在删除。如何让每个事务只能看到自己应该看到的数据呢?
首先我们想到,应该给每个事务编号,并且这个编号和每一行数据有一个对应关系,这样才能控制某个事务只能看某些数据。因此,INNODB在每行记录中存放了一个隐藏字段trx_id
,即最近一次插入或更新这条记录的事务id。
然后,每个事务应该知道自己当前能看哪些事务的数据,这里就要区分事务的隔离级别了,读已提交 显然要比可重复读能看到的数据多一些。Mysql 通过Read View来记录创建一个事务时,系统中其他事务的相关信息,并以此来判断当前事务对某条数据的可见性。
Read View以及可重复读的隔离原理
顾名思义,Read View 就是个"读视图",或者说事务在创建时"拍了个快照"。如果类比java的数据结构的话,可以把它看做事务中的一部分,一个内部类。
java
class ReadView {
trx_id[] m_ids; //表示在生成readview时,当前系统中活跃的读写事务id数组;
trx_id min_trx_id; /*表示在生成readview时,当前系统中活跃的读写事务中最小的事务id,
也就是m_ids中最小的值,低水位; */
trx_id max_trx_id; //表示生成readview时,系统中应该分配给下一个事务的id值,高水位;
trx_id creator_trx_id; //表示生成该readview的事务的事务id;
}
以上代码并不是真实的实现哈,只是为了方便理解,临时编写的一个类。
我们先以可重复读隔离级别为例,在Repeatable Read隔离级别下,Mysql将在启动事务时创建一个Read View,因此事务后续在访问数据记录时会有以下几种情况:
- 如果记录的 trx_id 值等于Read View中的creator_trx_id,也就是说这条记录就是当前事务创建的,显然该记录可见;
- 如果记录的 trx_id 值小于Read View中的min_trx_id,表示该版本的记录在本事务创建之前就有了,因此可见;
- 如果记录的 trx_id 值大于Read View中的max_trx_id,表示该版本的记录在本事务创建之后才创建,因此不可见;
- 如果记录的 trx_id 值介于min_trx_id和max_trx_id之间,就要进一步判断trx_id是否在数组m_ids中:
- 如果trx_id在数组m_ids中,表明生成该记录的事务还没提交,处于活跃状态,因此该记录对当前事务不可见;
- 如果trx_id不在数组m_ids中,表明生成该记录的事务已经被提交,因此该记录可见。
这时还有一个问题,就是某条数据因为事务状态的原因导致不可见,但是这条数据之前就存在了,也就是说当前事务应该看到这条数据之前的版本,这应该如何实现呢?
其实,在mysql的数据行中还有一个隐藏列,就是roll_pointer,每次事务对记录创建或修改时,都会将旧版本的数据计入undo日志,roll_pointer则负责以指针的形式连接这些不同版本的数据。当一个版本的trx_id不可见时,会顺着roll_pointer向以前的版本寻找,直到数据可见或没有数据。
读已提交是如何实现的?
上面描述了可重复读的工作原理,那读已提交是如何实现的呢?其实它俩的区别就一句话:
可重复读只在事务开始时创建一次Read View,读已提交会在每次读操作时创建Read View。
这就意味着,在可重复读隔离级别下,如果事务A在执行过程中,其他事务提交了数据,A相当于是完全不知道的,它将会读到事务开始时的数据;而在读已提交隔离级别下,每次读操作都会刷新Read View,里面的各项参数也随之被更新,其他事务提交的修改就可见了。
总结
本文主要介绍了MVCC的工作原理,以及数据中的隐藏列和Read View是如何配合的。需要注意的是,MVCC只解决了快照读的隔离问题(也就是普通的select语句),这对于读已提交隔离级别来说已经足够,因为当前读和快照读的表现是一样的。而对于可重复读隔离级别来说,当前读还会产生幻读和不可重复读现象,这需要加锁解决,该问题将在后续文章中阐述。