MVCC在Mysql中的运用

news2025/2/25 6:26:09

MVCC到底是个啥?

定义: 多版本并发控制,字面理解,在并发过程中利用多个版本进行合理控制(反正我就是从字面这么理解),很明显,这个东西是个抽象的概念,事实也是如此。它主要是出现在一些数据管理软件中。
维持一个数据的多个版本,使得读写操作没有冲突。

  • 为什么会有这个东西呢?

我们都知道,数据管理程序提供的功能就是对数据的查询和修改,但是读写过程中如何怎么解决冲突问题呢,为了维护数据的一致性且保持较高的性能,到即使有读写冲突时,也能做到不加锁,非阻塞并发读, MVCC 这种并发控制算法就出现了。

MVCC基础原理

MVCC 是允许一个对象的多个版本同时存在。也就是说,他拥有 “当前” 版本和一个或多个以前的版本。当你在获取版本时可以根据需要使用它的不同版本来解决你面对的问题。在此运行期间, “作者” 可以创建和发布新的对象版本,该版本将成为对象的最新版本, “读者” 依旧也可以使用之前的版本。

到底使用哪些版本提供给 “读者” 呢?这个就和你的真实需求相关了,例如 Mysql 中的隔离级别,不同级别对相同并发操作后的结果看到不一致,我们可以根据需求来合理展示数据对象的版本,在不同隔离级别上实现不同的效果。

既然知道了 MVCC 的抽象,我们再去看看它的实现, MVCC 被利拥到很多的数据管理程序上,无疑证明他是一个很好的设计思路,例如在 Mysql Innodb引擎 、 Etcd存储 、 PostgreSQLoracle 等等,我们就以 Innodb 引擎为例,观察一下他是如何实现 MVCC

Innodb引擎MVCC探究

Innodb 引擎中,我们需要先了解一些基础概念: undolog版本链 ReadView

undolog

我们知道,事务是具有原子性的,但是当系统故障,或者手动回滚时,如何保证这次提交的数据全部进行恢复呢,有时候都可能事务只执行到一半,我们要保证它和原来一样,这个事务看起来什么都没有做,举一个生活中的例子:如象棋,当下错子的时候可以申请悔棋, 悔棋 就是一种回滚操作,实际上就是之前执行的操作再次执行一次逆向操作,数据库中的回滚跟 悔棋 差不多,你插入一条记录,回滚操作日志对应的就是删除这个记录;你更新了一条记录,回滚操作对应的就是把该记录更新为旧值;你删除一条记录,对应回滚操作就是插入。

这些为了回滚记录的日志称为撤销日志 undolog ,在真实的 InnoDB 中, undo日志 其实并不像我们上边所说的那么简单,不同类型的操作产生的 undo日志 的格式也是不同的,不过先暂时把这些容易让人脑子糊的具体细节放一放,注意我们文章的重点,接着往下看。

版本链

版本链式用来存储该数据行的历史,每次对记录进行更新后,都会将旧值放到一条 undo日志 中,形成该记录的一个旧版本,随着更新次数的增加,所有版本都会被数据行的 roll_pointer 属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录的最新值。另外,每个版本还包含生成该版本的 事务ID ,大概逻辑如下图所示:

在这里插入图片描述

ReadView

RCRR 隔离级别中(READ UNCOMMITTED、SERIALIZABLE 是没有使用的Read View的),都必须保证已经读到已经提交了的事务修改过的记录,也就是说假设另一个记录已经修改了但是尚未提交,是不能直接读取最新版本的记录,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。Read View 这个概念就是为了解决这个问题,它包含了4个内容

  1. m_ids 生成 ReadView 时当前系统中活跃的读写事务ID列表
  2. min_trx_id 生成 ReadView 时当前系统中活跃事务最小的事务ID,即 m_ids 中的最小值
  3. max_trx_id 生成 ReadView 时系统中应该分配给下一个事务ID
  4. creator_trx_id 生成 ReadView 时的事务ID只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。
    有了这个 ReadView ,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
  • 如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  • 如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的 trx_id 属性值大于或等于 ReadView 中的max_trx_id值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_idmax_trx_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边步骤判断可见性。依次类推,直到版本链中的最后一个版本。如果最后一个版本都不可见的话,那就意味着该记录对该事务完全不可见,查询结果也就不包含该记录。

而生成 ReadView 的时机不同也直接影响了查询操作的结果,在 Mysql 中, RCRR 隔离级别最大的区别就是生成 ReadView 的时机不同。

READ COMMITTED —— 每次读取数据前都生成一个ReadView

比方说现在系统里有两个事务id分别为100、200的事务在执行:

# Transaction 100
BEGIN;

UPDATE hero SET name = '关羽' WHERE number = 1;

UPDATE hero SET name = '张飞' WHERE number = 1;
# Transaction 200
BEGIN;

# 更新了一些别的表的记录
...

小贴士: 再次强调一遍,事务执行过程中,只有在第一次真正修改记录时(比如使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id,这个事务id是递增的。所以我们才在Transaction 200中更新一些别的表的记录,目的是让它分配事务id。

此刻,表heronumber为1的记录得到的版本链表如下所示:

2FA19065196157DE

假设现在有一个使用READ COMMITTED隔离级别的事务开始执行:

# 使用READ COMMITTED隔离级别的事务
BEGIN;

# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'

这个SELECT1的执行过程如下:

  • 在执行SELECT语句时会先生成一个ReadViewReadViewm_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’张飞’,该版本的trx_id值为100,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’关羽’,该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’刘备’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’刘备’的记录。

之后,我们把事务id为100的事务提交一下,就像这样:


# Transaction 100
BEGIN;

UPDATE hero SET name = '关羽' WHERE number = 1;

UPDATE hero SET name = '张飞' WHERE number = 1;

COMMIT;

然后再到事务id为200的事务中更新一下表heronumber为1的记录:


# Transaction 200
BEGIN;

# 更新了一些别的表的记录
...

UPDATE hero SET name = '赵云' WHERE number = 1;

UPDATE hero SET name = '诸葛亮' WHERE number = 1;

此刻,表heronumber为1的记录的版本链就长这样:

B3638932E3A43EFF

然后再到刚才使用READ COMMITTED隔离级别的事务中继续查找这个number为1的记录,如下:


# 使用READ COMMITTED隔离级别的事务
BEGIN;

# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'

# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'张飞'

这个SELECT2的执行过程如下:

  • 在执行SELECT语句时会又会单独生成一个ReadView,该ReadViewm_ids列表的内容就是[200](事务id为100的那个事务已经提交了,所以再次生成快照时就没有它了),min_trx_id为200,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’诸葛亮’,该版本的trx_id值为200,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’赵云’,该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’张飞’,该版本的trx_id值为100,小于ReadView中的min_trx_id值200,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’张飞’的记录。

以此类推,如果之后事务id为200的记录也提交了,再次在使用READ COMMITTED隔离级别的事务中查询表hero中number值为1的记录时,得到的结果就是’诸葛亮’了,具体流程我们就不分析了。总结一下就是:使用READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。

REPEATABLE READ —— 在第一次读取数据时生成一个ReadView,在一个事务中保证多个查询结果一致

对于使用REPEATABLE READ隔离级别的事务来说,只会在第一次执行查询语句时生成一个ReadView,之后的查询就不会重复生成了。我们还是用例子看一下是什么效果。

比方说现在系统里有两个事务id分别为100、200的事务在执行:

# Transaction 100
BEGIN;

UPDATE hero SET name = '关羽' WHERE number = 1;

UPDATE hero SET name = '张飞' WHERE number = 1;
# Transaction 200
BEGIN;

# 更新了一些别的表的记录
...

此刻,表heronumber为1的记录得到的版本链表如下所示:

39D76CAE66B11201

假设现在有一个使用REPEATABLE READ隔离级别的事务开始执行:

# 使用REPEATABLE READ隔离级别的事务
BEGIN;

# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'

这个SELECT1的执行过程如下:

  • 在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’张飞’,该版本的trx_id值为100,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’关羽’,该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’刘备’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’刘备’的记录。

之后,我们把事务id为100的事务提交一下,就像这样:

# Transaction 100
BEGIN;

UPDATE hero SET name = '关羽' WHERE number = 1;

UPDATE hero SET name = '张飞' WHERE number = 1;

COMMIT;

然后再到事务id为200的事务中更新一下表hero中number为1的记录:


# Transaction 200
BEGIN;

# 更新了一些别的表的记录
...

UPDATE hero SET name = '赵云' WHERE number = 1;

UPDATE hero SET name = '诸葛亮' WHERE number = 1;

此刻,表heronumber为1的记录的版本链就长这样:

633F3DFAACD6CA11

然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找这个number为1的记录,如下:


# 使用REPEATABLE READ隔离级别的事务
BEGIN;

# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'

# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值仍为'刘备'

这个SELECT2的执行过程如下:

  • 因为当前事务的隔离级别为REPEATABLE READ,而之前在执行SELECT1时已经生成过ReadView了,所以此时直接复用之前的ReadView,之前的ReadView的m_ids列表的内容就是[100, 200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’诸葛亮’,该版本的trx_id值为200,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’赵云’,该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’张飞’,该版本的trx_id值为100,而m_ids列表中是包含值为100的事务id的,所以该版本也不符合要求,同理下一个列name的内容是’关羽’的版本也不符合要求。继续跳到下一个版本。
  • 下一个版本的列name的内容是’刘备’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为’刘备’的记录。

也就是说两次SELECT查询得到的结果是重复的,记录的列c值都是’刘备’,这就是可重复读的含义。如果我们之后再把事务id为200的记录提交了,然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找这个number为1的记录,得到的结果还是’刘备’,具体执行过程大家可以自己分析一下。

总结一下

看了上面的 undolog版本链ReadView 后,我们大致也有了一些想法, ReadView 主要是对记录的历史进行可见性规则制定和规则校验,而记录的历史则是采用链表结构存储在 undolog 个,三者结合解决了 MVCC 中的读写问题。而 mysql 中所谓的 MVCC 指的就是在使用 RC 和 RR 两种隔离级别的事务在执行普通 SELECT 操作时访问版本链的过程,这样子可以使不同事务的读写,写写操作并发执行,从而提升系统性能,而他们最大的不同点就在于生成 ReadView 的时机不同

References

[1] MySQL 是怎样运行的:从根儿上理解 MySQL: https://juejin.cn/book/6844733769996304392/section/6844733770071801870

参考:憧憬在 aoppp.com发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/619086.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenStack部署(二)

OpenStack部署 4. Glance4.1 创建Glance数据库并授权4.2 获得admin凭证4.3 创建glance用户并设置密码4.4 添加 admin 角色到 glance 用户和 service 项目上4.5 创建glance服务实体4.6 创建镜像服务的 API 端点4.7 yum安装Glance服务4.8 初始化镜像服务的数据库4.9 启动镜像服务…

中企出海,数智人力重构企智人效的人才供应体系

本文来自深度围观 中企出海一直是热度话题,综合来看,中企出海除了市场拓展、国际化战略、多元投资的因素之外,还有非常重要的一点是,全球供应链和资源整合。用友网络副总裁张月强总结为:“在产品国际化、区域经营国际…

在探索嵌入式系统世界的道路上选择51单片机

当我是一个初学者时,我发现选择51单片机是一个绝佳的决定。我发现51单片机基于Intel 8051架构,非常适合学习和教育领域的应用。刚好,我总结了一些嵌入式资料放在视频结尾。以下是为什么我认为51单片机是初学者的理想选择的一些原因&#xff1…

直播预告 | 在能媲美“真假美猴王”的AI面前,如何保持我们的“火眼金睛”

AI欺诈防护——业务安全大讲堂第二季第六期-CSDN直播https://live.csdn.net/room/dingxiangtech/7P3ME1HJ AI造谣层出不穷,险些引发社会恐慌 “2021年4月,上海某公司高管因对方使用AI换脸与人工生成的语音技术制作公司领导的面孔,并要该高管…

chatgpt赋能python:Python如何优化SEO?

Python如何优化SEO? Python已经成为一种非常流行的编程语言。专业人士使用Python编写众多应用程序,将其应用于各种行业和领域。众所周知,搜索引擎是市场营销的重要组成部分。SEO是在网站和搜索引擎结果页面上提高网站排名的过程。在这个过程…

ChatGPT时代:ChatGPT全能应用一本通

摘要 ChatGPT是一款开创性的人工智能语言模型,将人类语言理解和生成的能力推向了新的高度。作为一个全能的应用,ChatGPT能够在各个领域提供帮助和指导,从教育到医疗,从娱乐到商业。本文将探讨ChatGPT时代的到来,以及其…

三天吃透Spring面试八股文

摘自我的面试网站:topjavaer.cn Spring是什么? Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。最全面的Java面试网站:最全面的Java面试网站 Spring的优点 通过控制反转和依赖注入实现松耦合。支持面向切面的编程&#xff…

对话CEO:用高性价比AI视觉检测系统做客户坚盾,迎光伏行业新洗牌

“企业需要紧跟行业技术发展,要有前瞻性的预判和洞察,提前做好技术研发储备,下一阶段的光伏行业一定是更智能化的质效之争。我们能做的就是深入客户场景,将每一个细节做到极致,用高性价比的AI视觉产品为客户打造竞争的…

K8S之pod(十二)

一、简介 在Kubernetes集群中,Pod是所有业务类型的基础,也是K8S管理的最小单位级,它是一个或多个容器的组合。这些容器共享存储、网络和命名空间,以及如何运行的规范。在Pod中,所有容器都被统一安排和调度,…

《C++高级编程》读书笔记(四:设计专业的C++程序)

1、参考引用 C高级编程(第4版,C17标准)马克葛瑞格尔 2、建议先看《21天学通C》 这本书入门,笔记链接如下 21天学通C读书笔记(文章链接汇总) 1. 程序设计概述 在启动新程序(或已有程序的新功能&…

MMDetection环境配置与使用

在安装MMDetection时,耗费了近一天时间,其实安装很简单,只要保证环境对应即可(这不是废话吗),总而言之,只要严格按照步骤Windows下环境配置就是可行的。 Window环境配置 基础环境 CUDA为10.1 创建Conda环…

AWTK实现汽车仪表Cluster/DashBoard嵌入式GUI开发(三):移植

AWTK最大优势是什么?除了免费,一个是轻量级、速度快,还有一个就是跨平台,它是为移植而生,为嵌入式而生。 而嵌入式和桌面系统最大不同在于,桌面系统的CPU是intel/AMD的X86系统,操作系统时Windows/Linux,而嵌入式则更加多样,内核可能是ARM、RISC,不同厂家基于ARM内核设…

原来,网络机架的门道也这么多

大家好,我的网工朋友 前几天给你们说了机房搬迁,发现大家对硬件设备还挺感兴趣。还没看过的看这:《别小瞧,搬迁网络机房,讲究的可不少》。 之前大多给你分享技术和行业经验,这回来点“硬的”。 如果你做的…

{} >= {} 返回 true

JavaScript 一共提供了8个比较运算符。 相等比较 相等运算符 严格相等运算符 ! 不相等运算符 ! 严格不相等运算符 非相等比较 > 大于运算符 < 小于运算符 < 小于或等于运算符 > 大于或等于运算符 这八个比较运算符分成两类&#xff1a;相等比较和非相等比较。 两…

构建vue初始化项目:vue create 命令构建vue项目

首先找到自己的文件夹 1.创建vue项目&#xff1a;vue create vue 2.选择Manually select features自定义创建 3.选择vue版本(这里选择的是vue2) 4. 5. 6. 7. 8创建完成 创建完项目后先删除node_modules然后执行 npm设置淘宝镜像加速&#xff1a;npm config set registr…

java SSM 美食资讯网系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM 美食资讯网系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码 和数据库&#xff0c;系统主要采用…

chatgpt赋能python:Python如何设置画笔颜色255

Python如何设置画笔颜色255 Python是一种强大的编程语言&#xff0c;广泛应用于不同领域&#xff0c;尤其在数据分析、机器学习和人工智能方面出色。在数据可视化方面&#xff0c;Python提供了一些很好的库和函数&#xff0c;例如matplotlib和seaborn&#xff0c;这些库可以用…

使用TuyaOS幻彩灯带开发包快速开发一款智能幻彩灯带

使用TuyaOS快速开发一款智能幻彩灯带 一、亮点功能介绍二、开发包的核心优势1、丰富的基础服务和驱动2、支持Kconfig3、满足开发者自定义需求 4、支持与帮助1. 下载产品开发包2. 联系我们 如果你常看短视频&#xff0c;一定被各种炫酷的幻彩灯带产品刷屏过。随着智能幻彩灯带的…

“加密前行”-加密芯片在软件License中的应用

“ 在上篇文章中&#xff0c;我们介绍了在汽车应用中&#xff0c;软硬件加密技术在保护车辆数据和通信方面发挥着关键作用。 JokerEye&#xff0c;公众号&#xff1a;ADAS之眼 ADAS-“加密前行”:软硬件技术在汽车安全中的应用" 今天&#xff0c;我们将以实际的加密芯片案…

万物悦享推广方案范文

万物悦享推广方案范文&#xff0c;做好商城视觉优化&#xff0c;可以让你超过90%的商家#抖音商城 #抖音小店 #抖音电商 #电商干货 #干货分享 但问耕耘&#xff0c;莫问收获 优秀的人往往会寻找机遇 恭喜程总拿下成都市郫都区运营商 开启万物悦享财富 管道收益 扩展阅读&#x…