【MySQL】搞懂mvcc、read view:MySQL事务原理深度剖析

news2025/1/3 21:49:54

        前言:本节内容是事务里面最难的一部分, 就是理解mvcc快照读和read view。这两个部分需要了解隔离性里面的四种隔离级别。 博主之前讲过,但是担心友友们不了解, 所以这里开头进行了复习。 下面开始我们的学习吧!

        ps:本节内容很难, 希望友友们沉下心认真学习哦!

目录

一致性

三种并发方式的安全隐患

三个隐藏字段

Undo日志

mvcc多版本控制

read view

 RC VS RR的本质区别


我们先来回忆一下上接内容讲解的隔离性的四种隔离级别, 以及并发产生的问题:

  • 脏读:一个事务在执行中,读到另一个事务的更新但是还没有commit的数据, 这种情况叫做脏读。
  • 不可重复读:一个事务提交前和提交后, 另一个事务select查到的数据结果是不一样的。 也就是说一个事务一直select, 结果某一次select的时候, 数据突然变了。
  • 幻读:读者读者可能多出来了一条数据,就类似于出现了幻觉。 
  • 读未提交: 会导致脏读,不可重复读, 幻读现象。
  • 读提交 :解决了脏读, 但是有不可重复读, 幻读。
  • 可重复单独: 解决了脏读, 不可重复读, 但是有些数据库的insert会有幻读现象(mysql解决了幻读)
  • 串行化:加锁, 不进行并发执行, 没有上面的问题。

一致性

        事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一种一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而改变未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确的状态。因此一致性是通过原子性保证的。

三种并发方式的安全隐患

  • 读读并发:不存在任何问题,不需要并发控制。
  • 读写并发:有线程安全问题,可能造成事务隔离性问题可能遇到脏读,幻读,不可重复读
  • 写写并发:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失 

        读写并发,也就是一个事务在写,一个事务在读。要保证读到的是原来的,和另一个事务写的不一样。
        这个使用的是一种无锁的并发控制MVCC。

  • 1、每个事务都要有自己的事务ID,可以根据事务的大小,来决定事务到来的先后顺序。
  • 2、mysqld可能会面临处理多个事务的情况。事务在使用者的角度看来是原子的,但是本质上是有过程的。所以就证明事务也有自己的生命周期。一》mysqld要对事务进行管理:先描述,再组织。事务在我看来,mysqld中一定是对应的一个或者一套结构体对象/类对象
  • 3、所以,事务也要有自己的结构体。 

下面我们都在讲解mvcc以及版本链, 很重要, 很难理解, 需要反复观看。

三个隐藏字段

其实我们之前在建表的时候, mysql也会给我们添加三个默认的,隐藏的字段:

  • DB_TRX_ID:6byte,每一条数据后面都要有一个属性,就是DB_TRX_ID,无论是单sql,但是我们手动begin起的事务。都算是事务,都有自己的事务ID,而DB_TRX ID,就是最后操作这条数据的事务ID。
  • DB_ROLL_PTR:在MySQL中,对于某一行数据来说,我们要修改他,并不是直接就修改了。 mysql要先将数据保存一份,然后再改 这样的话,我们改完之后,我们也知道未来这个字段历史是谁。所以,为了保证能够找到这个最后的历史信息,就有了这个DB_ROLL_PTR。
  • DB_ROW_ID:隐藏的自增ID,如果数据库表中没有主键,In        noDB会自动以DB_ROW_ID产生一个聚族索引。大小6byte

Undo日志

        上面的红框框就是操作系统, 然后上层就是用户层。里面有我们的mysql的一个buffer pool , 然后undo日志就是在这个buffer pool里面。undo log其实就是mysql里面的一段内存缓冲区。作用就是用来记录一些mysql里面事务的回滚操作等。

        假如现在有一行数据如下:

        这个数据我们知道是在b+树的叶子节点上面。(这个b+树也在buffer pool里面),现在有一个事务10, 想要对这行数据进行修改, 将名字张三改成李四。       

        因为要修改, 所以先将这条记录加锁, 然后将这条数据记录复制一份到undo log里面:

        然后就开始修改, 现存数据name改成李四, 事务ID改成10, 然后回滚指针指向刚刚拷贝到undo log里面的数据。这就叫做历史版本。

        最后commit, 释放锁。 就行了。 (注意,这里示例是写写并发, 所以串行化, 加锁。)

        然后现在又来了一个事务11,也要进行修改, 要将这个age修改成28. 那么就和上面一样, 先拷贝一份数据到undo log里面。

        然后修改记录:

        所以, 我们就有了一个历史版本链, 如果未来我们不想修改了, 后悔了, 就可以把undo log里面的历史数据拿出来。 

mvcc多版本控制

        我们把上面的版本链, 这种多版本控制就叫做mvcc多版本控制。而里面的一个一个的版本的数据, 我们就叫做快照。 

        另外, undo log是一个临时缓冲区。 他管理的是一个事务内所形成的版本链条。 什么意思? 就是说, 一个事务里面, 我们多次修改,多次update,才会形成版本链, 当事务commit的时候, undo log 就会被清空, 也就是free掉。 同样的, delete数据也能形成版本链, 因为delete的本质是把改行数据的"flag“置为删除。 所以也可以形成版本链。

        问题是!insert可以形成版本链吗?——其实,insert可以理解为是没有版本链的,但是insert对应的数据也要放到undo log里面。但是如果一旦commit, 这个insert对应的版本连也就可以被清空了。 而对于undate和delete来说就不一定, 因为可能也有其他的事务在访问这一条记录。 

        我们上面说的是update和delete, 以及insert。 那么select呢?我们知道, update和delete的对象一定是最新数据, 历史数据他们没有权限修改。 那么select我们怎么知道我们读取的是最新数据还是历史数据呢? 所以就有了当前读和快照读的概念。 

  •         当前读:读取最新的记录, 就是当前读, 增删改,都叫做当前读。select也可能当前读。
  •         快照读:读取历史版本,就叫做快照读。

        那么现在再来看我们之前的读写操作。 我们做过实验, 看到的是两个事务, 一个写一个读。 然后两个事务看到的数据是不同的数据。——就是因为我们的读取, 读取的是历史的数据。 而写,写的是当前的数据。 两个事务在访问不同的位置, 就不需要进行加锁, 所以就不会发生并发的问题, 我们就可以并发进行读写操作。所以, 这就是之前我们为什么能够读写并发的原因!!! 

        所以, 隔离性, 本质上是数据上的隔离, 也是版本上面的隔离。这就是不同的隔离级别让我们看到不同的版本!!!

        所以, 隔离, 隔离性本质上使用mvcc实现的,我们的事务回滚利用的也是利用mvcc版本控制的。

read view

        read view我们讨论的是rc和rr这两种隔离级别。read view就是事务进行快照读操作的时候生产的读视图,在该事务执行快照读的那一刻,会生成数据库当前的一个快照,记录并维护系统当前活跃事务的ID。(当每个事务开启时,都会被分配给一个ID,这个ID是递增的。所以最新的事务的ID更大)

        read view是一个类,与事务的关系就类似于进程地址空间和PCB。两个数据结构之间使用指针连接起来。

        当我们进行快照读的时候,对该记录创建一个read view读视图。然后对它填充数据,作为条件。 用来判断当前事务能够看到哪个版本的数据,既可能是当前数据, 也可能是undo log里面的历史数据。下面看一下这个read view结构体:

class Readview
{
private:
    trx_id_t m_low_limit_id;  //高水位,大于等于这个ID的事务均不可见。
    trx_id_t m_up_limit_id;  //低水位, 小于这个ID的事务均可见。
    trx_id_t m_creator_trx_id;     //创建该Read view的事务ID。    
    ids_t m_ids; //创建视图时的活跃事务id列表。
    trx_id_t m_low_limit_no; 
    bool m_closed;  //视图是否关闭
    //....省略       
};

        上面重要的是两个水位线 , 事务id。m_ids就是当前正在打开的事务id列表。

现在一张图我们来理解read view:

        在这张图里面,先看已经提交的事务。 已经提交的事务,就是说我们当前的事务read view的时候,如果当前事务ID和我们看到的版本链里面的事务ID一样, 那么就说明我们可以看到这个版本的数据。如果版本链里面的事务ID比m_ids里面最小的事务ID都小, 那么也能看到。 因为ID小, 代表事务启动的早, 那么后启动的事务就应该能看到先启动的事务。

        再看快照后来的事务:我们说low_limit_id是当前事务read view后能看到的最晚启动的一个事务。 那么如果版本链里面的事务大于这个low_limit_id, 就不应该看到这个版本的数据。为什么会出现这种情况, 因为事务一定有先有后, 后来的事务ID一定大!

        然后就是看正在操作的事务, 这里要注意的是,正在操作的事务不一定是连续的。 因为虽然我们说ID是递增的, 但是事务的执行时间是不同的, 有的事务很快就跑完了, 有的事务很慢才能跑完。 所以就会出现图中的情况。 而我们形成快照后m_ids里面的版本如果和某个版本的数据相同,就说明这个ID的事务还是活跃的, 没有commit, 那么就不能看到。如果没有, 那么就能看到。 

        另外,我们还要注意的是!read view是事务可见性的一个类,不是事务创建出来, 就有read view, 而是这个已经存在的事务, 首次进行快照读的时候,mysql形成read view!

 RC VS RR的本质区别

        有了上面的read view的知识点之后, 我们就能谈RC和RR的本质区别了。我们谈论RC和RR的区别, 需要使用一个例子, 现在我们做几个实验。 
        首先第一个实验:

        这是account表。 并且此时的隔离级别是可重复读。然后我们两边都开启事务:

然后两边都进行快照读, 记录当前的read view:

接下来我们左边修改数据,然后commit:

然后我们查一下右边:

很显然, 因为是可重复读, 所以数据没有变化。 这也符合我们的预期。

现在我们来看一下实验二:

        同样是隔离级别为可重复读。 但是这次右边不进行快照读。 而是等到左边commit之后,在进行快照读:

查右边:

我们会发现, id = 4这个字段被修改了。 也就是说我们的右边看到了左边的修改。 

        这里为什么会发生这种情况, 是因为我们的read view是在快照读的时候就生成, 然后RR隔离界别的read view只会生成一次。 就是第一次快照读的时候。 所以在第一个实验中, 我们一开始就使用了快照读。 此时m_ids显示左右两个事务都是活跃事务, 那么我们进行判断的时候, 右边的事务对于左边事务修改的版本就看不见。 

        实验二的时候, 我们一开始没有快照读, 而是等到左边事务commit后才进行快照读。那么右边事务的m_ids里面就一定没有左边事务的ID, 并且,因为左边事务启动在右边事务快照读之前所以左边事务的事务ID也不可能比右边事务的事务的low_limt_id大。 那么这个时候左边事务的事务ID只有两种情况, 一种情况是比右边事务的up_limit_id还要小。或者是在up_limit_id和low_limit_id之间但是没在m_ids里面。 这两种情况下右边事务都可以看到左边事务的修改版本!!!所以右边事务就能看到左边事务的修改数据版本!!!

        上面是RR。RC就好说了, RR是只能生成一次read view, 只能进行一次快照。 后面的事务无论做什么修改, 都要根据这一个快照进行判断能不能够看到这些版本数据。 但是RC每次进行快照读都能生成快照, 每次快照读都生成快照, 那么low_limit_id就永远是当前系统下最大的事务ID。 所以就能保证只要其他事务commit了, 其他事务不是活跃事务了, 那么就只剩下了在up_limit_id和low_limit_id之间但是没在m_ids里面这两种情况。 也就一定能看到了。 所以这就是RC的形成原因。  这就是RR和RC的根本区别!

 

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!    

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

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

相关文章

jmeter设置tps、响应时间监测时间间隔

jmeter设置tps、响应时间监测时间间隔 思路: 1、设置tps和响应时间插件的采集时间间隔,然后运行jmeter脚本; 2、先按默认配置跑出jtl文件保存下来,再添加tps和响应时间插件,设置采集时间间隔后,导入jtl文件…

Qt 12.30 day5

day5_testppp.proQT core gui texttospeech widget.h#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent>//定时器事件类 #include <QTimer>//时间事件类 #include <QTime>//时间类 #include <QTextToSpeech>//…

玩转OCR | 腾讯云智能结构化OCR初体验

随着数字化进程的加速&#xff0c;光学字符识别&#xff08;OCR&#xff09;技术已逐渐成为提高企业生产力、优化工作流的重要工具。腾讯云智能结构化OCR凭借其领先的技术、广泛的应用场景和灵活的定制化能力&#xff0c;正在帮助各行业客户更高效地进行文档处理与数据提取。本…

Spring Boot教程之三十九: 使用 Maven 将 Spring Boot 应用程序 Docker 化

如何使用 Maven 将 Spring Boot 应用程序 Docker 化&#xff1f; Docker是一个开源容器化工具&#xff0c;用于在隔离环境中构建、运行和管理应用程序。它方便开发人员捆绑其软件、库和配置文件。Docker 有助于将一个容器与另一个容器隔离。在本文中&#xff0c;为了将Spring B…

模仿微信小程序wx.showModal自定义弹窗,内容可以修改

实现以下弹框样式功能 1.在components新建一个文件showModel.wpy作为组件&#xff0c;复制下面代码 <style lang"less" scoped> .bg_model {display: flex;justify-content: center;align-items: center;// 弹框背景.bg_hui {width: 100%;height: 100%;posi…

缓存管理自动化:JuiceFS 企业版 Cache Group Operator 新特性发布

近期&#xff0c;JuiceFS 企业版推出了 Cache Group Operator&#xff0c;用于自动化创建和管理缓存组集群。Operator 是一种简化 Kubernetes 应用管理的工具&#xff0c;它能够自动化应用程序的生命周期管理任务&#xff0c;使部署、扩展和运维更加高效。 在推出 Operator 之前…

零基础微信小程序开发——小程序的宿主环境(保姆级教程+超详细)

&#x1f3a5; 作者简介&#xff1a; CSDN\阿里云\腾讯云\华为云开发社区优质创作者&#xff0c;专注分享大数据、Python、数据库、人工智能等领域的优质内容 &#x1f338;个人主页&#xff1a; 长风清留杨的博客 &#x1f343;形式准则&#xff1a; 无论成就大小&#xff0c;…

GPU 进阶笔记(二):华为昇腾 910B GPU

大家读完觉得有意义记得关注和点赞&#xff01;&#xff01;&#xff01; 1 术语 1.1 与 NVIDIA 术语对应关系1.2 缩写2 产品与机器 2.1 GPU 产品2.2 训练机器 底座 CPU功耗操作系统2.3 性能3 实探&#xff1a;鲲鹏底座 8*910B GPU 主机 3.1 CPU3.2 网卡和网络3.3 GPU 信息 3.3…

微服务SpringCloud分布式事务之Seata

视频教程&#xff1a;https://www.bilibili.com/video/BV16P63Y3ESq 效果演示 准备的微服务项目调用的链路如下&#xff1a; 文字描述&#xff1a; gateway模块接收到请求&#xff0c;并发送到order订单模块order订单模块接收到请求&#xff0c;新增一个订单数据后发送一个…

HTML——13.超链接

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>超链接</title></head><body><!--超链接:从一个网页链接到另一个网页--><!--语法&#xff1a;<a href"淘宝网链接的地址"> 淘宝…

STM32 高级 WIFi案例1:测试AT指令

需求描述 测试AT指令是否能够正常控制ESP32的wifi&#xff0c;比如重启、读取设备信息等。 思路&#xff1a; stm32通过串口usart2向ESP32发布命令。ESP32通过串口1返回信息。 配置&#xff1a; 第一步&#xff1a;对ESP32芯片烧录可以读取stm32命令的固件&#xff08;fac…

GXUOJ-算法-第四次作业(圆排列、连续邮资、n皇后、符号三角形)

1.圆排列 问题描述 GXUOJ | 圆排列 代码解答 #include<bits/stdc.h>using namespace std;int n; int r[1000]; double x[1000]; double ans 0x3f3f3f3f;double calculate(){memset(x, 0, sizeof x);for(int i 0; i < n; i){for(int j 0; j < i; j)x[i] max(…

[创业之路-225]:《华为闭环战略管理》-4-华为的商业智慧:在价值链中探索取舍之道与企业边界

目录 一、在价值链中探索取舍之道与企业边界 价值链的深刻洞察 取舍之道&#xff1a;有所为&#xff0c;有所不为 垂直整合与横向整合的平衡 企业边界与活动边界的界定 采购与外包的智慧运用 结语 二、企业外部价值流&#xff1a;上游、中游、下游、终端 上游&#xf…

【从零开始入门unity游戏开发之——C#篇33】C#委托(`Delegate`)和事件(`event` )、事件与委托的区别、Invoke()的解释

文章目录 一、委托&#xff08;Delegate&#xff09;1、什么是委托&#xff1f;2、委托的基本语法3、定义自定义委托4、如何使用自定义委托5、多播委托6、C# 中的系统委托7、GetInvocationList 获取多个函数返回值8、总结 二、事件&#xff08;event &#xff09;1、事件是什么…

大模型—Ollama 结构化输出

Ollama 结构化输出 Ollama现在支持结构化输出,使得可以按照由JSON模式定义的特定格式来约束模型的输出。Ollama的Python和JavaScript库已经更新,以支持结构化输出。 结构化输出的用例包括: 从文档中解析数据从图像中提取数据结构化所有语言模型响应比JSON模式更可靠和一致开…

【Python】selenium 获取滑块和背景图在电脑屏幕的相对位置【解决滑块验证码问题】

在使用python模拟爬取的时候&#xff0c;解决滑动验证码是不可缺少的环节。怎么解决滑动验证码的问题呢&#xff0c;我们可以试着使用这样的方法&#xff1a; 我们在解决滑块验证码问题的时候&#xff0c;首先要找到滑块和在网页上的位置&#xff0c;定位好位置以后&#xff0…

微信小程序:定义页面标题,动态设置页面标题,json

1、常规设置页面标题 正常微信小程序中&#xff0c;设置页面标题再json页面中进行设置&#xff0c;例如 {"usingComponents": {},"navigationBarTitleText": "标题","navigationBarBackgroundColor": "#78b7f7","navi…

Visual Studio 中增加的AI功能

前言&#xff1a; 人工智能的发展&#xff0c;在现在&#xff0c;编程技术的IDE里面也融合了AI的基本操做。本例&#xff0c;以微软的Visual Studio中的人工智能的功能介绍例子。 本例的环境&#xff1a; Visual Studio 17.12 1 AI 智能变量检测&#xff1a; 上图展示了一…

后端开发-Maven

环境说明&#xff1a; windows系统&#xff1a;11版本 idea版本&#xff1a;2023.3.2 Maven 介绍 Apache Maven 是一个 Java 项目的构建管理和理解工具。Maven 使用一个项目对象模型&#xff08;POM&#xff09;&#xff0c;通过一组构建规则和约定来管理项目的构建&#xf…

BAPI_BATCH_CHANGE在更新后不自动更新批次特征

1、问题介绍 在CL03中看到分类特性配置了制造日期字段&#xff0c;并绑定了生产日期字段MCH1~HSDAT MSC2N修改批次的生产日期字段时&#xff0c;自动修改了对应的批次特性 但是通过BAPI&#xff1a;BAPI_BATCH_CHANGE修改生产日期时&#xff0c;并没有更新到批次特性中 2、BAPI…