五、MVCC实现三大要素
终于来到本文最重要的部分,前边的叙述都是为了给原理这一块做铺垫。
在这之前需要知道MVCC只在REPEATABLE READ(可重复读) 和 READ COMMITTED(已读提交)这俩种隔离级别下适用。
MVCC实现原理是由俩个隐式字段、undo日志、Read view来实现的。
1. 隐式字段
在Innodb存储引擎中,在有聚簇索引的情况下每一行记录中都会隐藏俩个字段,如果没有聚簇索引则还有一个6byte的隐藏主键。
这俩个隐藏列一个记录的是何时被创建的,一个记录的是什么时候被删除。
这里不要理解为是记录的是时间,存储的是事务ID。
俩个隐式字段为DB_TRX_ID,DB_ROLL_PTR,没有聚簇索引还会有DB_ROW_ID这个字段。
DB_TRX_ID:记录创建这条数据上次修改它的事务 ID
DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本
隐式字段实际还有一个delete flag字段,即记录被更新或删除,这里的删除并不代表真的删除,而是将这条记录的delete flag改为true(这里埋下一个伏笔,数据库的删除是真的删除吗?)
2. 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和一致性视图做对比,从而得到快照结果。
3. undo log底层实现
假设一开始的数据为下图
此时执行了一条更新的SQL语句update user set name = 'niuniu where id = 1',那么undo log的记录就会发生变化
也就是说当执行一条更新语句时会把之前的原有数据拷贝到undo log日志中。
同时你可以看见最新的一条记录在末尾处连接了一条线,也就是说DB_ROLL_PTR记录的就是存放在undo log日志的指针地址。
最终有可能需要通过指针来找到历史数据。
4. read-view
当执行SQL语句查询时会产生一致性视图,也就是read-view,它是由查询的那一时间所有未提交事务ID组成的数组,和已经创建的最大事务ID组成的。
在这个数组中最小的事务ID被称之为min_id,最大事务ID被称之为max_id,查询的数据结果要根据read-view做对比从而得到快照结果。
于是就产生了以下的对比规则,这个规则就是使用当前的记录的trx_id跟read-view进行对比,对比规则如下。
5. 版本链对比规则
如果落在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,意味着数据已经被删除,则不返回数据。
如果你对这里的read-view的生成和版本链对比规则不懂,不要着急,也不要在这里浪费时间,请继续往下看,咔咔会使用一个简单的案例和一个复杂的案例给大家重现上述的规则。
六、MVCC底层原理
案例一
下图是准备的素材,这里应该都理解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这个值
先通过这个简单的案例让你对版本链有一个简单的理解,接下来将使用一个比较繁琐的案例再来跟大家演示一遍。