一句话描述:名为多版本并发控制的MVCC实际上就是保存了数据在某个时间节点的快照
在提到MVCC之前,不得不提下undo_log
MVCC的实现,主要是通过undo log日志版本链,和read view 来实现来实现。
roll_pointer指向更新事务之前生成的undo log,undo log用于事务的回滚,保证事务的原子性。trx_id就是最近一次更新数据的事务ID。
执行查询时,就会开启一个read view,read view包含几个重要的东西。
- m_ids,就是还未提交的事务id集合
- low_limit_id,m_ids里最小的值
- up_limit_id,下一次生成事务ID最大值
- creator_trx_id,创建read view的事务ID,也就是自己的事务ID
因此会出现已下几种情况:
- 如果trx_id<low_limit_id,那么说明就是之前已经提交事务的数据,直接返回。
- 如果trx_id>low_limit,trx_id还在[ low_limit_id , up_limit_id ]范围之内,并在m_ids列表中,就说明,该数据被未提交的事务所更改,就会根据roll_pointer去查找undo log日志链,找到之前版本的数据。
- 如果trx_id=creator_trx_id,那么说明就是自己修改的,直接返回就好了。
以上的逻辑很明显,是解决已提交读时会出现的不可重复读的问题,即保证了可重复读的隔离级别。
那么如果隔离级别是已提交读呢?
可重复读级别时,假设每次查询的时候生成了read view,后续并没有重新生成。
而读已提交级别下,则是每次查询都会生成一次read view。
因此当事务两次查询A,B之间,如果其他事务做出了提交,那么该行数据的trx_id是不会出现在B的m_ids中的,因此修改的新数据可以被读取到。
可他不是用来解决幻读的吗?
幻读其实是最简单的应用了,这是之前说过的:
- 如果trx_id>low_limit,trx_id还在[ low_limit_id , up_limit_id ]范围之内,并在m_ids列表中,就说明,该数据被未提交的事务所更改,就会根据roll_pointer去查找undo log日志链,找到之前版本的数据。
那么如果是新的事务做出了添加,但未提交的话,他的trx_id就会在m_ids中,此时则去日志链中寻找,并发现没有这个数据。
如果已经提交了的话,那么也可以通过trx_id和creator_trx_id进行比较的方式去淘汰这行。
文章末尾请带上以下文字及链接:本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情