MySQL MVCC机制详解
MySQL MVCC机制详解
MVCC, 是Multi Version Concurrency Control的缩写,其含义是多版本并发控制。这一概念的提出是为了使得MySQL可以实现RC隔离级别和RR隔离级别。
这里回顾一下MySQL的事务, MySQL的隔离级别和各种隔离级别所存在的问题。
事务是由 MySQL 的引擎来实现的,我们常见的 InnoDB 引擎它是支持事务的。
不过并不是所有的引擎都能支持事务,比如 MySQL 原生的 MyISAM 引擎就不支持事务,也正是这样,所以大多数 MySQL 的引擎都是用 InnoDB。
事务看起来感觉简单,但是要实现事务必须要遵守 4 个特性,分别如下:
- 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样;
- 一致性(Consistency):数据库的完整性不会因为事务的执行而受到破坏,比如表中有一个字段为姓名,它有唯一约束,也就是表中姓名不能重复,如果一个事务对姓名字段进行了修改,但是在事务提交后,表中的姓名变得非唯一性了,这就破坏了事务的一致性要求,这时数据库就要撤销该事务,返回初始化的状态。
- 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
- 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
MySQL的四种隔离级别如下:
- 读未提交(read uncommitted):指一个事务还没有提交时,它做的变更才能被其他事务看到;
- 读提交(read committed),指一个事务提交之后,它所做的变更才能被其他事务看到
- 可重复度(repeated read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据时一致的,这是MySQL InnoDB引擎的默认隔离级别。
- 串行化(serializable):会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行
读未提交级别下会遇到脏读的问题,所谓脏读是指在一个事务中会读取到另一个事务没有提交的改动,例如下图中所示:

A用户在第一次查询ID=1的用户时,其年龄是10。 在这之后,B用户对ID=1的用户的age进行了修改,随后就将事务进行了回滚。但是结果A用户第二次查询ID=1的用户的年龄时发现年龄修改为了20, 即读取到了脏数据。
读提交级别下会遇到不可重复读的问题,所谓不可重复读是指在同一个事务中多次select出的数据的值发生了变化。例如下图中所示:

A用户在第一次查询ID=1的用户时,其年龄是10。 在这之后,B用户对ID=1的用户的age进行了修改,并且提交了事务,结果A用户第二次查询ID=1的用户的年龄时发现年龄修改为了20。这样的变化就是不可重复读。
MySQL使用了MVCC实现了RC和RR隔离级别,这便是MVCC机制的作用。
为了更好的去理解MVCC的原理,我们需要对MySQL的undo log有一些理解。
undo log
undo log是MySQL的三大日志之一,另外两个是bin log和redo log。
undo log译名为回滚日志,也就是用于事务回滚的日志。在事务没有提交之前, MySQL会将用户的操作记录到回滚日志中,如果用户执行了回滚操作,则根据回滚日志执行反向操作,例如:
- 如果用户向数据库插入了一条数据,回滚时执行反向操作,即删除该条数据。
- 如果用户删除了数据库的一条数据,回滚时执行反向操作,则向数据库插入该条数据。
- 如果用户更新的一条记录,则需要把原值记录下来,回滚时则执行反向操作,将该数据的值恢复为原值。
不知道看到上面的操作有没有让你联想到git revert。git是一个版本管理工具, git log便是记录了仓库的所有commit的记录。根据git的某一个commit,git revert便会生成其反向的操作。
其实undo log的思想和git是类似的。其通过隐藏列trx_id、roll_pointer将不同事务的commit按照时间线组织了起来。
隐藏列trx_id、roll_pointer的含义如下表所示:
|列名|是否必须|描述|
|trx_id|是|记录操作该行数据事务的事务ID|
|roll_pointer|是|回滚指针,指向当前记录行的undo log信息|
如下图所示,通过roll_pointer就将每个commit串成了一个版本链。

这样的版本链便给后续的ReadView的生成提供了条件。
ReadView
ReadView类似于一个snapshot(快照),ReadView是基于undo log实现的。
下面就来看看ReadView具体是如何实现的。
ReadView记录了下面一些字段:
- creator_trx_id: 创建该ReadView的事务的id
- m_ids: 创建ReadView时,当前数据库活跃且未提交的事务id列表
- up_limit_id: 创建ReadView时,当前数据库中活跃且未提交的最小事务id
- low_limit_id: 创建ReadView时,当前数据库中分配的下一个事务的id值
利用ReadView中的这些字段就可以判断undo log版本链上的每个commit对于当前的事务而言是否是可见的。
对于undo log中的某一条记录,判断其是否可见的规则如下:
- 如果被访问版本的 事务ID = creator_trx_id,那么表示当前事务访问的是自己修改过的记录,那么该版本对当前事务可见;
- 如果被访问版本的 事务ID < up_limit_id,那么表示生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的 事务ID > low_limit_id 值,那么表示生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
- 如果被访问版本的 事务ID在 up_limit_id和m_low_limit_id 之间,那就需要判断一下版本的事务ID是不是在 trx_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
这段逻辑写在MySQL仓库的storage/innobase/include/read0types.h文件中。
/** Check whether the changes by id are visible.@param[in] id transaction id to check against the view@param[in] name table name@return whether the view sees the modifications of id. */[[nodiscard]] bool changes_visible(trx_id_t id,const table_name_t &name) const {//ut 忽略ut_ad(id > 0);//如果被访问版本的 事务ID = creator_trx_id,那么表示当前事务访问的是自己修改过的记录,那么该版本对当前事务可见;//如果被访问版本的 事务ID < up_limit_id,那么表示生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。if (id < m_up_limit_id || id == m_creator_trx_id) {return (true);}check_trx_id_sanity(id, name);//如果被访问版本的 事务ID > low_limit_id 值,那么表示生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。if (id >= m_low_limit_id) {return (false);//如果m_ids为空,则生成readview时所有的commit对于当前事务都可见} else if (m_ids.empty()) {return (true);}const ids_t::value_type *p = m_ids.data();//如果被访问版本的 事务ID在 up_limit_id和m_low_limit_id 之间,那就需要判断一下版本的事务ID是不是在 trx_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。return (!std::binary_search(p, p + m_ids.size(), id));}
通过源码的阅读, 也印证了上述匹配逻辑。
上面的匹配的逻辑是针对单条commit记录的。整个过程将从undo log的最新记录开始,逐条判断,如果判断结果是可见的,那么则返回该记录。如果判断结果是不可见的,则沿着undo log往下继续寻找。
整个寻找的过程可以参照下面的流程图:

下面通过一些案列来加深ReadView的理解。
在下面的案例中,事务8是当前的事务,其使用了select语句查询了表中的数据,触发了readview的生成,因此creator_trx_id=8。在readview生成的时刻,当前活跃的且未提交的事务为[4,6,7,9], 因此up_limit_id=4, low_limit_id=11。

下面查看user表的id=1的undo log,其最新的改动是事务9提交的。 事务9满足下面的不等式,事务4 < 事务9 < 事务11, 因此需要查看事务9是否在trx_ids列表中。经过查看发现事务9在m_ids中,因此在生成readview的时刻,事务9的提交对于事务8并不可见。 因此需要往下滑动,检查undo log中次新的数据。
在undo log的次新的数据中。trx_id=8, 与creator_trx_id相等,因此对于当前事务可见。因此readview中可见的最新数据已经找到。

下面查看user表的id=1的undo log,其最新的改动是事务12提交的。 事务12 > low_limit_id, 事务12的提交对于事务8并不可见。 因此需要往下滑动,检查undo log中次新的数据。
在undo log的次新的数据中。trx_id=10,在 up_limit_id和m_low_limit_id 之间,且事务10不在m_ids,说明创建 ReadView 时生成事务10已经被提交,该版本可以被访问。因此因此readview中可见的最新数据已经找到。

下面再看一个例子,在该例子中,undo log中最新的记录的事务id是6, 事务6满足下面的不等式, 事务4 < 事务6 < 事务11, 因此下面就需要检查事务6是否在m_ids中, 因为m_ids = [4, 6, 7, 9],因此事务6在创建readview时还没有提交,因此对于当前事务而言,该条记录并不可见。 因此沿着undo log往下找。
undo log中第二新的记录的事务id是14,事务14 > low_limit_id, 显而易见, 事务14的改动对于当前事务是不可见的。因此继续undo log往下找。
undo log中第三新的记录的事务id是5,事务4 < 事务5 < 事务11, 显而易见, 因此下面就需要检查事务5是否在m_ids中, 因为m_ids = [4, 6, 7, 9],因此事务5在创建readview时已经提交了,于是事务5对于当前事务而言是可见的, 于是找到了所需的值。

通过这三个案列对MVCC的工作机制会有非常深刻的理解了。
MVCC如何实现读提交和可重复读
读提交和可重复读的MVCC机制是相同的。区别在于ReadView的生成时机不同。
对于读提交级别而言,其会在每一次查询操作时生成一次ReadView。因此后续再次select时,就可以读取到这期间的提交。
对于可重复读级别而言,其只会在事务的第一次查询操作时生成ReadView, 于是在ReadView生成后提交的commit就不再会看到,就好像是在对一个snapshot操作一样。

参考文章
https://www.cnblogs.com/qdhxhz/p/15750866.html
https://www.cnblogs.com/cswiki/p/15338928.html
https://www.6hu.cc/archives/86666.html
相关文章:
MySQL MVCC机制详解
MySQL MVCC机制详解 MVCC, 是Multi Version Concurrency Control的缩写,其含义是多版本并发控制。这一概念的提出是为了使得MySQL可以实现RC隔离级别和RR隔离级别。 这里回顾一下MySQL的事务, MySQL的隔离级别和各种隔离级别所存在的问题。 事务是由 …...
搭建成功simulink-stm32硬件在环开发环境
本次实验所使用的软件版本和硬件平台参数如下: Matlab版本: 2021b STM32硬件平台:YF_STM32_Alpha 1R4(参考自STM32 Nucleo F103RB官方开发板) YF_STM32_Alpha开发板 STM32 Nucleo F103RB 开发板 2.1 STM32硬件支持包下载 读者朋友平时使用的是和谐版M…...
【计算机网络】UDP协议
UDP的结构 我们学习一个协议最主要的就是理解它的报文格式,对于UDP协议来说 我们看下面的这张图。 16位UDP长度,表示整个数据报(UDP首部UDP数据)的最大长度。UDP报文长度占两个字节,16位表示的数据范围(0-…...
ubuntu安装mysql8.0.35过程和报错处理
ubuntu安装mysql8.0.35过程 1.更新包列表:首先,确保您的系统已更新到最新状态。运行以下命令来更新包列表和安装最新的软件包: sudo apt update sudo apt upgrade2.安装MySQL服务器:运行以下命令来安装MySQL服务器: …...
SQL基础理论篇(一):什么是SQL
文章目录 什么是SQLSQL的四大部分常用的SQL标准参考文献 什么是SQL SQL的全称是Structured Query Language,即结构化查询语句。 其最早诞生于1974年,IBM研究员发布的一篇论文"SEQUEL:一门结构化的英语查询语言"。这几十年里&…...
物联网AI MicroPython学习之语法 GPIO输入输出模块
学物联网,来万物简单IoT物联网!! GPIO 介绍 模块功能: GPIO通用输入输出。 接口说明 GPIO - 构建GPIO对象 函数原型:Pin(port, dir , pull)参数说明: 参数类型必选参数?说明portintY对应开发板的引脚号…...
phalcon 访问IndexController 中只能访问indexAction方法,访问不了testAction等其它问题的解决办法
phalcon 访问IndexController 中只能访问indexAction方法,访问不了testAction,也访问不了indexAction方法,但是可以访问ArticleController里面的任意方法。访问其它方法出现这个错误“php - phalcon IndexController handler class cannot be loaded” 有人说是Apache 的rew…...
docker安装AWVS 23.9.231005181
本文声明仅AWVS用作学习使用 将镜像文件secfa_awvs.tar复制到目标机器上。 我的百度网盘文件路径: 链接:https://pan.baidu.com/s/1Pe4qlVp9XKbZ3dLrouaP2w 提取码:67mc –来自百度网盘超级会员V6的分享 在目标机器上,使用以下命…...
数据同步工具调研选型:SeaTunnel 与 DataX 、Sqoop、Flume、Flink CDC 对比
产品概述 Apache SeaTunnel 是一个非常易用的超高性能分布式数据集成产品,支持海量数据的离线及实时同步。每天可稳定高效同步万亿级数据,已应用于数百家企业生产,也是首个由国人主导贡献到 Apache 基金会的数据集成顶级项目。 SeaTunnel 主…...
【Vue】Vue3 Swiper 插件 loop 无限滚动、并且暂停的问题
上午把官网的合作伙伴做了,好坑,swiper 自动滚动展示的数量 slides-per-view 的两倍必须小于等于 *SwiperSlide* 组件的渲染数量,才能进行自动滚动,官网居然都没有说。 比如 slidesPerView 6,那么 SwiperSlide 组件渲…...
MySQL的DATE_FORMAT函数使用
在MySQL中,可以使用DATE_FORMAT函数将日期格式化为所需的格式。DATE_FORMAT函数接受两个参数:日期和格式字符串。 以下是一些常用的日期格式化选项: %Y:四位数的年份(例如:2023)%y:…...
MySQL的SQL预编译及防SQL注入
文章目录 1 SQL语句的执行处理1.1 即时SQL1.2 预处理SQL1.2.1 预编译SQL的实现步骤1.2.2 预编译SQL的C使用举例1.2.3 MYSQL_BIND()函数中的参数类型: 2 SQL注入2.1 什么是SQL注入2.2 如何防止SQL注入 1 SQL语句的执行处理 SQL的执行可大致分为下面两种模式…...
博流BL602芯片 - 烧录配置
硬件介绍 淘宝上买的核心板,大概结构如上。 直接插入电脑usb,即可实现供电、下载(控制BOOT/EN)、串口通讯 固件包 1、环境配置 1.1串口 开发板使用了 CH340G 的 USB 转串口芯片,自行安装CH340串口驱动。 1.2编译环境…...
websocket实现实时数据推送,发布订阅重连单点登录功能
需求:使用websocket不借助插件实现发布,订阅,网络断开重连,单点登录后挤号的功能 1.单点登录(同一账号同一时间只有一个在线,禁止多用户登录) 实现:在用户登录之后获取到token令牌并…...
前端代理模式之【策略模式】
文章目录 前言介绍代码场景例子优缺点后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:前端设计模式 🐱👓博主在前端领域还有很多知识和技术需要掌握,正在不断努力填补技术短板。(如果出现错误&#…...
人工智能-深度学习之残差网络(ResNet)
随着我们设计越来越深的网络,深刻理解“新添加的层如何提升神经网络的性能”变得至关重要。更重要的是设计网络的能力,在这种网络中,添加层会使网络更具表现力, 为了取得质的突破,我们需要一些数学基础知识。 ResNet沿…...
arm2 day6
串口实现单个字符的收发 main.c uart4.c uart4.h...
RxSwift和Combine的相同点和使用例子
RxSwift 和 Combine 都是响应式编程框架,用于简化异步和基于事件的代码。它们有很多相似之处,主要体现在设计理念和编程模式上。以下是 RxSwift 和 Combine 的主要相同点,以及它们的应用场景: 相同点 1.响应式编程:两…...
[Linux打怪升级之路]-信号的保存和递达
前言 作者:小蜗牛向前冲 名言:我可以接受失败,但我不能接受放弃 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、信号的保…...
【科研新手指南3】chatgpt辅助论文优化表达
chatgpt辅助论文优化表达 写在最前面最终版什么是好的论文整体上:逻辑/连贯性细节上一些具体的修改例子 一些建议,包括具体的提问范例1. 明确你的需求2. 提供上下文信息3. 明确问题类型4. 测试不同建议5. 请求详细解释综合提问范例: 常规技巧…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
