关于MVCC,我之前写错了,这次我改好了!
关于MVCC的原理,在《我想进大厂》之mysql夺命连环13问写过一次,但是当时写的其实并不准确,这个理解可以应付面试,帮助快速理解,但是他的真正实现原理我想再次拿出来说一说。
简单理解版
以下先引用我之前写过的那篇中的内容,可以快速理解,建议先简单看看。
要说幻读,首先要了解MVCC,MVCC叫做多版本并发控制,实际上就是保存了数据在某个时间节点的快照。
我们每行数据实际上隐藏了两列,创建时间版本号,过期(删除)时间版本号,每开始一个新的事务,版本号都会自动递增。
还是拿上面的user表举例子,假设我们插入两条数据,他们实际上应该长这样。

这时候假设小明去执行查询,此时current_version=3
select * from user where id<=3;
同时,小红在这时候开启事务去修改id=1的记录,current_version=4
update user set name='张三三' where id=1;
执行成功后的结果是这样的

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

由于MVCC的原理是查找创建版本小于或等于当前事务版本,删除版本为空或者大于当前事务版本,小明的真实的查询应该是这样
select * from user where id<=3 and create_version<=3 and (delete_version>3 or delete_version is null);
所以小明最后查询到的id=1的名字还是'张三',并且id=2的记录也能查询到。这样做是为了保证事务读取的数据是在事务开始前就已经存在的,要么是事务自己插入或者修改的。
真正原理
事实上,上述的说法只是简化版的理解,真正的MVCC用于读已提交和可重复读级别的控制,主要通过undo log日志版本链和read view来实现。
每条数据隐藏的两个字段也并不是创建时间版本号和过期(删除)时间版本号,而是roll_pointer和trx_id。
roll_pointer指向更新事务之前生成的undo log,undo log用于事务的回滚,保证事务的原子性。
trx_id就是最近一次更新数据的事务ID。
以上述例子来举例,最初插入两条数据,真实的情况是这样,因为第一次插入数据没有undo log,所以roll_pointer指向一个空的undo log。

这时候假设小明去执行查询,就会开启一个read view,read view包含几个重要的东西。
- m_ids,就是还未提交的事务id集合
 - low_limit_id,m_ids里最小的值
 - up_limit_id,下一次生成事务ID最大值
 - 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,就是自己,那么就直接返回这条数据。
所以,我们可以先总结下几种情况:
- 如果trx_id<low_limit_id,那么说明就是之前事务的数据,直接返回,也就对应了小明第一次开启事务查询的场景
 - 如果trx_id>low_limit,trx_id还在[low_limit_id,up_limit_id]范围之内,并且trx_id在m_ids中,就会根据roll_pointer去查找undo log日志链,找到之前版本的数据,对应的就是小红修改后小明再次查询的场景
 - 如果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 -
关于MVCC,我之前写错了,这次我改好了!的更多相关文章
- 我发现 Linux 文档写错了
		
作者:小林coding 图解计算机基础网站:https://xiaolincoding.com 大家好,我是小林. 周末的时候,有位读者疑惑为什么 Linux man 手册中关于 netstat 命令 ...
 - 如果$.ajax函数迟迟得不到响应,那么最有可能出错的地方是请求参数写错了
		
如下的$.ajax函数 $.ajax({ url: url,// 请求的地址 data:{id:id,pieceId:pieceId,pieceDesc:pieceDesc,actualStock:a ...
 - 原来你一直写错了?!实力分享一波 CSS 使用的书写规范顺序与偏门又实用的样式...
		
我们在埋头写代码的时候,还要学会收集整理一些常用的代码小技巧,以便在工作时候,可以及时调取,提高工作效率. 今天,我把之前收集整理的一些CSS代码小技巧分享出来,供你参考学习,希望对你有帮助. 一.C ...
 - 汝佳大神的紫书上写错了?uva10048
		
算法竞赛入门经典第二版的365页例题11-5噪音.应该是"之和"换成"取最大值","取最小值"还是取最小值 假设我错了,请大家务必指点小弟 ...
 - 没想到吧!关于Dubbo的『消费端线程池模型』官网也写错了。
		
这是why的第 63 篇原创文章 荒腔走板 大家好,我是 why,欢迎来到我连续周更优质原创文章的第 63 篇.老规矩,先荒腔走板聊聊其他的. 上面这张图片是我前几天整理相册的时候看到的.拍摄于 20 ...
 - Ajax中最有名axios插件(只应用于Ajax)(post方法,官网写错了,应是字符串格式)
		
/* axios v0.18.0 | (c) 2018 by Matt Zabriskie */!function(e,t){"object"==typeof exports&am ...
 - var foo = "11"+2+"1"; console.log(foo); //1121  好多文章答案写错了,我发下给初学的朋友看到,以免一开始就学错了
		
体会加一个字符串'1' 和 减去一个字符串'1'的不同 var foo = "11"+2-"1"; console.log(foo); //111 consol ...
 - Mac OS X下把 /etc/sudoers 写错了怎么办?(转载https://blog.csdn.net/robertsong2004/article/details/53725285)
		
重要的事情先说一下,首先为了回避这个问题,一定要用 visudo 来改 /etc/sudoers 文件. 问题描述: 1. 用 sudo vi 直接改 /etc/sudoers 并覆盖原文件. 2. ...
 - 2018-2019-2 网络对抗技术 20165239 Exp2 后门原理与实践
		
一.实验要求 (3.5分) (1)使用netcat获取主机操作Shell,cron启动 (0.5分) (2)使用socat获取主机操作Shell, 任务计划启动 (0.5分) (3)使用MSF met ...
 
随机推荐
- java判断是否为整数
			
/** * 判断是否为整数 * * @param str 传入的字符串 * @return 是整数返回true,否则返回false */ public static boolean isInteger ...
 - 6. Linux输入输出重定向
			
1.输入重定向是指把文件导入到命令中,而输出重定向则是指把原本要输出到屏幕的数据信息写入到指定文件中. 输入重定向中用到的符号及其作用 输出重定向中用到的符号及其作用 1)通过输出重定向将原本要输出到 ...
 - 如何用RabbitMQ实现延迟队列
			
前言 在 jdk 的 juc 工具包中,提供了一种延迟队列 DelayQueue.延迟队列用处非常广泛,比如我们最常见的场景就是在网购或者外卖平台中发起一个订单,如果不付款,一般 15 分钟后就会被关 ...
 - Kibana,Logstash 和 Cerebro 的安装运行
			
公号:码农充电站pro 主页:https://codeshellme.github.io 1,安装 Kibana Kibana 用于数据可视化,我们可以进入到 Kibana 下载页面下载 Kibana ...
 - 加快你ROS安装的一篇文章
			
前言: 首先ROS大家应该比较熟悉了哈,如果需要补充一下请看我之前的这篇文章 <嵌入式的我们为什么要学ROS>,对于嵌入式来说ROS是一个很好的进阶方向,所以如何快速的安装一个ROS到我们 ...
 - c++bind函数使用
			
总述 最近写代码的时候看到代码使用了bind,一个参数绑定的标准库函数.程序是这么写的, speaker_play_routine_ = new boost::thread (boost::b ...
 - KMP && Manacher && 扩展KMP整理
			
KMP算法: kmp示例代码: void cal_next(char *str, int *next, int len) { next[0] = -1;//next[0]初始化为-1,-1表示不存在相 ...
 - vs2019激活码
			
Visual Studio 2019 Enterprise BF8Y8-GN2QH-T84XB-QVY3B-RC4DF Visual Studio 2019 Professional NYWVH-HT ...
 - .Net下的PDF打印
			
简单研究了一下.Net下的PDF打印,一路发现了很多小坑. 第三方组件 这里使用的解析PDF的组件是mupdf,特点和C#调用在 这里 有介绍. 实现的功能 支持页面大小.边距.打印机选择.打印机dp ...
 - CF1462-E1. Close Tuples (easy version)
			
题意: 给出一个由n个数字组成的数组,先让你找出符合下列条件的子集的数量: 每个子集包含的数字个数为m = 3 这三个数字中的最大值减去最小值不超过k = 2 思路: 首先对给出的数组进行排序,现在假 ...