一文精通MVCC机制
MVCC(Multi-Version Concurrency Control)多版本并发控制机制
使用串行化隔离级别时,mysql会将所有的操作加锁互斥,来保证并发安全。这种方式必然降低并发性能。mysql在读已提交和可重复读隔离级别下,对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥。那么具体是如何实现的呢?首先要了解两个概念。
准备
建表语句
CREATE TABLE `product` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `price` int DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
undo日志版本链
我们向product表插入一条数据
INSERT INTO mysql_demo.product (id, name, price) VALUES (1, 'apple', 10);

此时mysql会同时向undo日志里写入一条记录。 trx_id为插入操作的事务id。这里随便写了一个80,意思一下。 roll_pointer后面再说。

这时候又来了一个事务,对数据进行了修改。比如事务id 300,修改price为20。此时mysql同样会在undo日志里写入一条记录。并且roll_pointer会指向前一条记录

以此类推,后续又有新的事务来操作这条记录,就会形成一条版本链,这条链就是undo日志版本链。

每条数据对应着有一个undo日志版本链。
对于insert和update操作,mysql会向undo日志里添加一条记录。select操作不会产生记录。
对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的(deleted_flag)标记位写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对应的记录如果delete_flag标记位为true,意味着记录已被删除,则不返回数据。
在来看下什么是read view。
一致性试图read view机制
read view的生成
可重复读隔离级别:事务开启后,首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。
读已提交隔离级别:事务开启后,每次执行select时都会重新生成read-view。
read view的组成
这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成。

我们来举个例子。
Transaction 80: 开启事务,插入一条记录。并且commit;
Transaction 100:开启事务,执行update。生成事务id 100。这里需要注意begin和select不会生成事务id,所以加了一条无关的update,生成事务id。update内容可以忽略。
begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,事务才真正启动,才会向mysql申请事务id
mysql内部是严格按照事务的启动顺序来分配事务id的
Transaction 200:同上
Transaction 300:把价格修改成20了。并且commit了。
select 1: select 不生成事务id。 事务开启后,首次执行任何select时会生成当前事务的read-view。
Transaction 400:把价格修改成18了。
read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成
此时未提交事务id有100,200(80 已经提交了)。最小的id为100。 已创建的最大事务id为300。(注意read view 是在第5步生成的,此时还没有Transaction 400)
因此 read view为[100,200],300 min_id为100 ,max_id为300。 [100,200] 为视图数组。
此时对应的undo日志版本链如下

那么read view 的作用是什么呢?
read view的作用
根据上面的结果,我们可以将事务进行分类。因为事务的id是有序递增的。所以我们可以得出以下结论
因为未提交事务的最小id(min_id)为100,所以小于100的事务都是已提交的。( Transaction 80)
因为已创建的最大事务id(max_id)为300,所以大于300的区域都是未开启事务。 (Transaction 400) 未开启理解为在执行select的时候没有开启。
介于min_id和max_id之间的事务,包含了未提交和已提交的事务。 (Transaction 100,200,300)

那么mysql是如何通过read view和undo日志版本链实现并发事务之间的隔离的呢?那就需要看下版本链比对规则了。
版本链比对规则
事务里的每一条select都需要从对应版本链里的最新数据开始逐条跟read-view做比对,按照比对规则得到最终的快照结果。下面我们来看下版本链比对规则。
如果 row 的 trx_id 落在绿色部分( trx_id
如果 row 的 trx_id 落在灰色部分( trx_id>max_id ),表示这个版本是由将来启动的事务生成的
row 的 trx_id 就是当前自己的事务是可见的;
否则不可见;
如果 row 的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括两种情况
若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,
若 row 的 trx_id 就是当前自己的事务是可见的
否则不可见;
若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。
知道了版本链的比对规则,下面我们通过实例来看下,mysql的MVCC机制是如何工作的。
实战演练
可重复读Repeatable-Read(RR)
我们先以可重复读Repeatable-Read(RR)为例
可重复读隔离级别:事务开启后,首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。
案例一
我们先以上面的情况为例来进行分析。此时的情况如下:

read view为 [100,200],300

undo日志版本链如下

套用版本链比对规则
首先在版本链中找到最新数据。
Transaction 300,trx_id = max_id。此时继续比对, trx_id 不在视图数组中,可见。
返回Transaction 300记录的数据信息。price = 20;
案例二

Transaction 400,在第10行执行了一次update。
Transaction 100,在第11,12行执行了两次update。然后select 1 13行执行了一次select。 我们来分析下这个select。
因为RR隔离级别首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。所以read view为 [100,200],300。没有变化。

undo日志版本链如下

套用版本链比对规则
首先在版本链中找到最新数据。
Transaction 100,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
第二行Transaction 100,分析同上
第三行Transaction 400, trx_id > max_id,不可见。
Transaction 300,trx_id = max_id。此时继续比对,trx_id 不在视图数组中,可见。
返回Transaction 300记录的数据信息。price = 20;
案例三

继续向下Transaction 100,在第15行commit。Transaction 200,在第15,16行执行了两次update。然后select1 17行执行了一次select。 我们来分析下这个select。
因为RR隔离级别首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。所以read view为 [100,200],300。没有变化。

undo日志版本链如下

套用版本链比对规则
首先在版本链中找到最新数据。
Transaction 200,min_id < trx_id < max_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
下一行Transaction 200,分析同上.
Transaction 100,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
下一行Transaction 100,分析同上。
下一行Transaction 400, trx_id > max_id,不可见。
Transaction 300,trx_id = max_id。此时继续比对,trx_id 不在视图数组中,可见。
返回Transaction 300记录的数据信息。price = 20;
案例四

继续select2 17行执行了一次select。 我们来分析下这个select。
RR隔离级别首次执行任何select时会生成当前事务的read-view。read view为 [200,400],400。

undo日志版本链如下

套用版本链比对规则
首先在版本链中找到最新数据。
Transaction 200,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
下一行Transaction 200,分析同上.
Transaction 100,trx_id < min_id。表示这个版本是已提交的事务生成的,这个数据是可见的;
返回 price = 16。
案例五
我们再来看一下如果select1 如果有update操作(update操作会创建事务id,我们假设是 500)。Transaction 500 此时是如何读取到更新后的数据的。

来分析下15行。
RR隔离级别首次执行任何select时会生成当前事务的read-view,在事务结束前不会变化。read view为 [100,200],300。

undo日志版本链如下

套用版本链比对规则
首先在版本链中找到最新数据。
Transaction 400,trx_id > max_id(read view是第一次select时生成的,此时max_id仍然是 300)。表示这个版本是由将来启动的事务生成的,是不可见的
Transaction 500,trx_id > max_id。表示这个版本是由将来启动的事务生成的,但row 的 trx_id 就是当前自己的事务是可见的;所以可见
返回 price = 8。
结论:通过以上案例,我们可以知道。 MVCC机制在RR中首次查询时会固定read view。后续和其他事务隔离开了,其他事务对数据的操作不会影响到当前事务。
读已提交Read-Committed(RC)
我们再以读已提交Read-Committed(RC)为例
读已提交隔离级别:事务开启后,每次执行select时都会重新生成read-view。
案例一

第9行没有变化,我们来看第13行。
read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成
未提交事务id数组 100,200,400 ; min_id 100 ; max_id 400
read view为 [100,200,400],400。

undo日志版本链如下

套用版本链比对规则
首先在版本链中找到最新数据。
Transaction 100,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
下一行Transaction 100,分析同上.
Transaction 400, trx_id = max_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
Transaction 300,min_id < trx_id< max_id。不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。
返回 price = 20。
案例二

来看第17行。
read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成
未提交事务id数组 200,400 ; min_id 200 ; max_id 400
read view为 [200,400],400。

undo日志版本链如下

套用版本链比对规则
首先在版本链中找到最新数据。
Transaction 200, trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
同上
Transaction 100, trx_id
返回 price = 16。
OK,就分析到这里吧。希望对你有所帮助!
读已提交Read-Committed(RC)
我们再以读已提交Read-Committed(RC)为例
读已提交隔离级别:事务开启后,每次执行select时都会重新生成read-view。
案例一

第9行没有变化,我们来看第13行。
read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成
未提交事务id数组 100,200,400 ; min_id 100 ; max_id 400
read view为 [100,200,400],400。

undo日志版本链如下

套用版本链比对规则
首先在版本链中找到最新数据。
Transaction 100,trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
下一行Transaction 100,分析同上.
Transaction 400, trx_id = max_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
Transaction 300,min_id < trx_id< max_id。不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。
返回 price = 20。
案例二

来看第17行。
read view的组成 = 未提交事务id数组(数组里最小的id为min_id) + 已创建的最大事务id(max_id)组成
未提交事务id数组 200,400 ; min_id 200 ; max_id 400
read view为 [200,400],400。

undo日志版本链如下

套用版本链比对规则
首先在版本链中找到最新数据。
Transaction 200, trx_id = min_id。继续分析 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见。
同上
Transaction 100, trx_id
返回 price = 16。
OK,就分析到这里吧。希望对你有所帮助!
相关文章:

一文精通MVCC机制
MVCC(Multi-Version Concurrency Control)多版本并发控制机制使用串行化隔离级别时,mysql会将所有的操作加锁互斥,来保证并发安全。这种方式必然降低并发性能。mysql在读已提交和可重复读隔离级别下,对一行数据的读和写两个操作默认是不会通过…...
商用ESP32协议采集器源码分享开篇
这是一个关于chatGPT帮助嵌入式程序员开发商业项目的故事. 在开发这个项目的过程中,chatGPT发布了,在它的帮助下,项目开发量减少了10%,所以这个专栏,既是一个关于Micropython开发ESP32的专栏,也是一个程序员在AI的帮助下,提升效率,加速挣钱的案例. 看完之后,你将知道如何用mic…...

代码随想录算法训练营第三十四天 | 860.柠檬水找零,406.根据身高重建队列,452. 用最少数量的箭引爆气球
一、参考资料柠檬水找零https://programmercarl.com/0860.%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.html 根据身高重建队列 https://programmercarl.com/0406.%E6%A0%B9%E6%8D%AE%E8%BA%AB%E9%AB%98%E9%87%8D%E5%BB%BA%E9%98%9F%E5%88%97.html 用最少数量的箭引爆气球ht…...

DDR4介绍01
DDR4(第四代双倍数据率同步动态随机存储器SDRAM) 关于内存方面知识,大部分人、包括我自己也不是很懂,希望此篇文章能起到点作用,做硬件的就得把相关专业知识学牢了,尤其是专业术语。 下面是DDR4知识做一次…...
扫地机器人行业投资逻辑:国内以价换量元年,海外需求企稳回升
1、国内以价换量元年,投资逻辑由产品迭代转向行业的渗透率提升 2019-2022 年国内扫地机行业主要的投资逻辑是产品迭代的价增带动销额增长。 2019-2022 年国内热销的扫地机产品从单机向自清洁扫地机、全能基站扫地机持续迭 代升级,产品功能日益完善、瞄准用户痛点更新,真正实…...

(考研湖科大教书匠计算机网络)第四章网络层-第七节:IPv4数据报首部格式
获取pdf:密码7281专栏目录首页:【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一:IP数据报首部格式概述二:各字段作用概述(1)版本(2)首部长度和可选字段(3&am…...

每天10个前端小知识 【Day 18】
前端面试基础知识题 1.如何实现单行/多行文本溢出的省略样式? 在日常开发展示页面,如果一段文本的数量过长,受制于元素宽度的因素,有可能不能完全显示,为了提高用户的使用体验,这个时候就需要…...

【Java集合类】ArrayList
内部结构 ArrayList内部核心是一个Object数组elementDataObject数组的长度(length)视为ArrayList当前的容量(capacity)size对象表示ArrayList当前的元素个数 类上的重要注释 内部是Object数组 允许put null值,会自动扩容 size、…...

页面置换算法
页面置换算法 在进程运行过程中,若需要访问的物理块不在内存中,就需要通过一定的方式来将页面载入内存,而此时内存很可能已无空闲空间,因此就需要一定的算法来选择内存中要被置换的页面,这种算法就被称为页面置换算法…...

算法导论【在线算法】—The Ski-Rental Problem、The Lost Cow Problem、The Secretary Problem
算法导论【在线算法】The Ski-Rental Problem问题描述在线算法证明The Lost Cow Problem问题描述在线算法类似问题—寻宝藏The Secretary Problem问题描述在线算法The Best Possible kThe Ski-Rental Problem 问题描述 假设你正在上滑雪课。每节课结束后,你决定&a…...
linux 下怎样给pdf 文件加书签
linux 下怎样给pdf 文件加书签 对于没有书签的pdf文件,怎样给pdf加标签呢? 以方便阅读. 以前总是要借助windows下pdf 工具, 叫什么来者? 忘了 记得是编辑一个用tab表示目录级别的文本文件, 有一种直观的感觉,大目录下嵌套着小目录 ..., 然后导入到文件中 linux 下有没有这种…...

[软件工程导论(第六版)]第2章 可行性研究(课后习题详解)
文章目录1. 在软件开发的早期阶段为什么要进行可行性研究?应该从哪些方面研究目标系统的可行性?2. 为方便储户,某银行拟开发计算机储蓄系统。储户填写的存款单或取款单由业务员输入系统,如果是存款,系统记录存款人姓名…...

[软件工程导论(第六版)]第3章 需求分析(课后习题详解)
文章目录1. 为什么要进行需求分析?通常对软件系统有哪些需求?2. 怎样与用户有效地沟通以获取用户的真实需求?3. 银行计算机储蓄系统的工作过程大致如下:储户填写的存款单或取款单由业务员输入系统,如果是存款则系统记录…...

基于分布鲁棒联合机会约束的能源和储备调度(Matlab代码实现)
👨🎓个人主页:研学社的博客 💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜…...
ETL和数据建模
一、什么是ETL ETL是数据抽取(Extract)、转换(Transform)、加载(Load )的简写,它是将OLTP系统中的数据经过抽取,并将不同数据源的数据进行转换、整合,得出一致性的数据&…...

ccc-pytorch-回归问题(1)
文章目录1.简单回归实战:2.手写数据识别1.简单回归实战: 用 线性回归拟合二维平面中的100个点 公式:ywxbywxbywxb 损失函数:∑(yreally−y)2\sum(y_{really}-y)^2∑(yreally−y)2 迭代方法:梯度下降法,…...

【JAVA八股文】框架相关
框架相关1. Spring refresh 流程2. Spring bean 生命周期3. Spring bean 循环依赖解决 set 循环依赖的原理4. Spring 事务失效5. Spring MVC 执行流程6. Spring 注解7. SpringBoot 自动配置原理8. Spring 中的设计模式1. Spring refresh 流程 Spring refresh 概述 refresh 是…...

二叉树的相关列题!!
对于二叉树,很难,很难!笔者也是感觉很难!虽然能听懂课程,但是,对于大部分的练习题并不能做出来!所以感觉很尴尬!!因此,笔者经过先前的那篇博客,已…...

Java设计模式 - 原型模式
简介 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直…...
深度学习中的 “Hello World“
Here’s an interesting fact—Each month, there are 186.000 Google searches for the keyword “deep learning.” 大家好✨,这里是bio🦖。每月有超18万的人使用谷歌搜索深度学习这一关键词,是什么让人们对深度学习如此感兴趣?接下来请跟随我来揭开深度学习的神秘面纱。…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...