1、什么是MVCC?
MVCC是在并发访问数据库时,通过对数据做多版本管理,避免因为写锁的阻塞而造成读数据的并发阻塞问题。
通俗的讲就是MVCC通过保存数据的历史版本,根据比较版本号来处理数据的是否显示,从而达到读取数据的时候不需要加锁就可以保证事务隔离性的效果
2、Innodb MVCC实现的核心知识点
-
undo log(回滚日志) 之前对undo log的作用只提到了回滚操作实现原子性,现在需要知道的另一个作用就是实现MVCC多版本控制器。 undo log细分为俩种,insert时产生的undo log、update,delete时产生的undo log 在Innodb中insert产生的undo log在提交事务之后就会被删除,因为新插入的数据没有历史版本,所以无需维护undo log。 update和delete操作产生的undo log都属于一种类型,在事务回滚时需要,而且在快照读时也需要,则需要维护多个版本信息。只有在快照读和事务回滚不涉及该日志时,对应的日志才会被purge线程统一删除。 purge线程会清理undo log的历史版本,同样也会清理del flag标记的记录。 undo log在mvcc中的作用 写到这里关于undo log在mvcc中的作用估计还是蒙圈的。 undo log保存的是一个版本链,也就是使用DB_ROLL_PTR这个字段来连接的。 当数据库执行一个select语句时会产生一致性视图read view。 那么这个read view是由查询时所有未提交事务ID组成的数组,数组中最小的事务ID为min_id和已创建的最大事务ID为max_id组成,查询的数据结果需要跟read-view做比较从而得到快照结果。 所以说undo log在mvcc中的作用就是为了根据存储的事务ID和一致性视图做对比,从而得到快照结果。 -
Read view 当执行SQL语句查询时会产生一致性视图,也就是read-view,它是由查询的那一时间所有未提交事务ID组成的数组,和已经创建的最大事务ID组成的。 在这个数组中最小的事务ID被称之为min_id,最大事务ID被称之为max_id,查询的数据结果要根据read-view做对比从而得到快照结果。 于是就产生了以下的对比规则,这个规则就是使用当前的记录的trx_id跟read-view进行对比,对比规则如下。 -
版本链对比规则
-
如果落在trx_id<min_id,表示此版本是已经提交的事务生成的,由于事务已经提交所以数据是可见的 -
如果落在trx_id>max_id,表示此版本是由将来启动的事务生成的,是肯定不可见的 若在min_id<=trx_id<=max_id时 -
如果row的trx_id在数组中,表示此版本是由还没提交的事务生成的,不可见,但是当前自己的事务是可见的 -
如果row的trx_id不在数组中,表明是提交的事务生成了该版本,可见 在这里还有一个特殊情况那就是对于已经删除的数据,在之前的undo log日志讲述时说了update和delete是同一种类型的undo log,同样也可以认为delete就是update的特殊情况。
当删除一条数据时会将版本链上最新的数据复制一份,然后将trx_id修改为删除时的trx_id,同时在该记录的头信息中存在一个delete flag标记,将这个标记写上true,用来表示当前记录已经删除。 在查询时按照版本链的规则查询到对应的记录,如果delete flag标记位为true,意味着数据已经被删除,则不返回数据。
案例一:
下图是准备的素材,这里应该都理解select 返回的结果为niuniu,即事务102修改后的结果 从上图中可以看到有三个事务正在进行。
事务ID为100、101是修改的其它表,只有事务ID为102修改的需要查询的这张表。
接下来看看select这一列查询返回的结果是不是就是事务ID为102修改的结果。
此时生成的read-view为[100,101],102
那么现在就可以返回去看一下read-view规则,在这里事务ID100就是min_id,事务ID102就是max_id。
这个 select语句返回结果肯定是 niuniu。
那么接下来看一下在MVCC中是如何查找数据的。
当前版本链。 那么就会拿着trx_id 为102进行比对,会发现这个102就是max_id
然后你再看一下版本链的对比规则中第三种情况
如果落在min_id<=trx_id<=max_id会存在俩种情况
此时信息就已经非常明确了,事务ID102是没有在数组中的,所以表示这个版本是已经提交的事务生成的,那么就是可见的呗!
毫无疑问查询会返回niuniu这个值
先通过这个简单的案例让你对版本链有一个简单的理解,接下来将使用一个比较繁琐的案例再来跟大家演示一遍。
案例二
本例要求知道 select的第二个查询结果。深黑色字体。
同样是在kaka那一条记录的基础上。 当事务ID100两次更新后,版本链也会改变,现在的版本链如下图,红色部分为最新数据,蓝色数据为undo log的版本链数据。 对于此时生成的read-view你会有什么疑问,在RR级别也就是可重复读的隔离级别下。
当在一个事务下执行查询时,所有的read-view都是沿用的第一条查询语句生成的。
那此时的read-view也就是[100,101],102
看一下底层查找步骤:
目前数据的事务ID为100 根据规则会落在min_id<=trx_id<=max_id这个区间 并且当前行的事务ID100是在read-view的数组中的,表示此时事务还没有提交则不可见 继续在版本链中往下寻找,此时找到的事务ID还是100,跟上述流程一致 通过查找版本链,将发现事务 ID为102 102是read-view的max_id,同样也会落在min_id<=trx_id<=max_id这个区间,但是跟之前不同的是事务102是没有在数组中的,表示这个版本事务已经提交了所以是可见的 最后返回的是 niuniu
同一个事务中进行查询,会沿用第一次查询语句生成的read-view(前提是隔离级别是在可重复读)
通过以上的案例,在版本链寻找过程中,可以总结出一个小技巧 根据这个小技巧你可以很快的得知此版本是否可见。
- 如果当前的事务ID在绿色部分,是已经提交事务,则数据可见
- 如果当前的事务ID在蓝色部分,会有俩种情况,如果当前事务ID在read-view数组内,是没有提交的事务不可见,如果不在数组内数据可见
- 如果落在红色部分,则不考虑,对于未来的事情不去想即可。
|