关于MVCC,我以前写错了,此次我改好了!

关于MVCC的原理,在《我想进大厂》之mysql夺命连环13问写过一次,可是当时写的其实并不许确,这个理解能够应付面试,帮助快速理解,可是他的真正实现原理我想再次拿出来讲一说。mysql

简单理解版

如下先引用我以前写过的那篇中的内容,能够快速理解,建议先简单看看。面试

要说幻读,首先要了解MVCC,MVCC叫作多版本并发控制,实际上就是保存了数据在某个时间节点的快照。sql

咱们每行数据实际上隐藏了两列,建立时间版本号,过时(删除)时间版本号,每开始一个新的事务,版本号都会自动递增。并发

仍是拿上面的user表举例子,假设咱们插入两条数据,他们实际上应该长这样。编辑器

这时候假设小明去执行查询,此时current_version=3spa

select * from user where id<=3;

同时,小红在这时候开启事务去修改id=1的记录,current_version=4日志

update user set name='张三三' where id=1;

执行成功后的结果是这样的code

若是这时候还有小黑在删除id=2的数据,current_version=5,执行后结果是这样的。blog

因为MVCC的原理是查找建立版本小于或等于当前事务版本,删除版本为空或者大于当前事务版本,小明的真实的查询应该是这样事务

select * from user where id<=and create_version<=and (delete_version>or delete_version is null);

因此小明最后查询到的id=1的名字仍是'张三',而且id=2的记录也能查询到。这样作是为了保证事务读取的数据是在事务开始前就已经存在的,要么是事务本身插入或者修改的

真正原理

事实上,上述的说法只是简化版的理解,真正的MVCC用于读已提交和可重复读级别的控制,主要经过undo log日志版本链和read view来实现。

每条数据隐藏的两个字段也并非建立时间版本号过时(删除)时间版本号,而是roll_pointertrx_id

roll_pointer指向更新事务以前生成的undo log,undo log用于事务的回滚,保证事务的原子性。

trx_id就是最近一次更新数据的事务ID。

以上述例子来举例,最初插入两条数据,真实的状况是这样,由于第一次插入数据没有undo log,因此roll_pointer指向一个空的undo log。

这时候假设小明去执行查询,就会开启一个read view,read view包含几个重要的东西。

  1. m_ids,就是还未提交的事务id集合
  2. low_limit_id,m_ids里最小的值
  3. up_limit_id,下一次生成事务ID最大值
  4. creator_trx_id,建立read view的事务ID,也就是本身的事务ID

小明来执行查询了,当前事务ID=3

select * from user where id<=3;

小红在这时候开启事务去修改id=1的记录,事务ID=4

update user set name='张三三' where id=1;

这时候小明的read view是这样。

m_ids=[3,4]

low_limit_id=3

up_limit_id=5

creator_trx_id=3

因此,小明在执行查询的时候,会去判断当前这条数据的trx_id<read view的low_limit_id,显然都小于,因此小明会正常查询到id=1,2的两条记录,而不会受到小红修改的影响。

这时候,小红的修改也完成了,小红数据因而就变成了这样。

若是小明再次去查询的话,就会发现如今的trx_id>read view的low_limit_id,也就是4>3,不符合条件,同时发现如今的trx_id=4在low_limit_id和up_limit_id [3,5]之间,而且trx_id=4在m_ids=[3,4]之中,因此就会根据roll_pointer指向的undo log去查找,trx_id=1小于如今的low_limit_id=3,符合条件,就找到了上一个版本name=张三的记录。

若是这时候小明本身去修改这条记录的值,把名字改为张五,结果就是这样。

而后小明去查询的话,就会发现当前的trx_id=3就是本身的creator_trx_id,就是本身,那么就直接返回这条数据。

因此,咱们能够先总结下几种状况:

  1. 若是trx_id<low_limit_id,那么说明就是以前事务的数据,直接返回,也就对应了小明第一次开启事务查询的场景
  2. 若是trx_id>low_limit,trx_id还在[low_limit_id,up_limit_id]范围以内,而且trx_id在m_ids中,就会根据roll_pointer去查找undo log日志链,找到以前版本的数据,对应的就是小红修改后小明再次查询的场景
  3. 若是trx_id=creator_trx_id,那么说明就是本身修改的,直接返回就行了,对应的就是小明本身去修改数据的场景

不一样隔离级别的实现

根据上面阐述的原理,你可能发现了,这是可重复读下的实现啊,保证每次读取到的数据都是一致的。

那么,若是是读已提交级别下,这个是怎么实现的?

其实很简单,在上面的原理解释中,我都是假设每次查询的时候生成了read view,后续并无从新生成。

而读已提交级别下,则是每次查询都会生成一次read view。

以上述小红修改过张三后的场景来举例。

在可重复度级别下,因为trx_id>low_limit,trx_id还在[low_limit_id,up_limit_id]范围以内,而且trx_id在m_ids中,知足咱们上述的条件2,因此就会根据roll_pointer找到以前的版本记录,保证可重复读。

而在读已提交的级别下,从新生成了read view,这时候trx_id不在m_ids之中,说明事务已经提交,因此能够直接返回这条数据,因此查到的数据就是小红修改后的name=张三三的数据了。

总结

我是艾小仙,我认可我浪了,我以前竟然还想浪,我觉得年没过几天,结果发现最近一次技术文更新是在2月2号。

我哭,因此,我肝了3个小时,痛定思痛,结束了个人短暂的王者生涯。

你们以为还行的话,点个在看,设个星标可好?

我要回到正常更新的频率中来。

- END -

相关文章
相关标签/搜索