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. 请求详细解释综合提问范例: 常规技巧…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...