MVCC机制解析:提升数据库并发性能的关键

MVCC机制解析:提升数据库并发性能的关键

MVCC(Multi-Version Concurrency Control) 多版本并发控制

MVCC只在事务隔离级别为读已提交(Read Committed)和可重复读(Repeated Read)下生效。

MVCC是做什么用的

MVCC是为了处理 可重复读读已提交 事务隔离级别下,在同一事务里,多次执行同一SQL查询语句,不会因为其他事务的横插一脚,对数据进行修改后,导致最终得到的结果不一致。如下图所示,事务B的两次查询得到的结果不一样。

请添加图片描述

MVCC是怎么实现的

串行读 这一事务隔离级别里面,为了保证较高的事务隔离性,采用了将所有的操作加锁互斥,将事务的执行变为顺序执行,相当于单线程的方式,以达到其高隔离性。

而mysql在 可重复读已提交读 事务隔离级别下,他的隔离性是借助MVCC机制来保证的,MVCC机制呢,也不是通过加锁互斥来保证隔离性的,是通过 Undo日志版本链Read View机制 实现的。避免了频繁的加锁互斥阻塞。

Undo日志版本链

Undo日志是什么?

undo日志是回滚日志,在mysql对某一数据进行修改更新时,会将其更新前的数据保存的undo回滚日志里,等当事务执行失败时,用来进行数据回滚的。

而什么是undo日志版本链呢?

就是一行数据被多个事务依次修改后,这条数据就会有很多条undo日志,这些undo日志就会通过一个 roll_pointer 字段进行串联起来,会形成这条数据的历史记录版本链,这个就是undo日志版本链。

roll_pointer 字段哪里来的呢?

在mysql数据库里,数据表都会有两个隐藏的字段属性, trx_id事务idroll_pointer回滚指针 ,其中 trx_id 是用来记录操作数据的,而 roll_pointer 则是用来记录指向上一次修改的日志地址。

注意:begin/start transaction这个开始/启动事务命令,并不是一个事务的起点, 而是在执行到第一条更新update、删除delete、插入insert语句时,事务才真正的启动,才会有真正的事务id。

后生成的trx_id要比先生成的trx_id大。

只有InnoDB数据引擎才支持事务。

下图中,每一条数据都是一个undo回滚日志,通过roll_pointer串联起来后,就是undo日志版本链了。

请添加图片描述

如果第二次修改操作将name改为了 王五 ,又因为某些原因需要进行数据回滚,就要拿到roll_pointer里记录的上一次,也就是第一次修改操作的undo回滚日志地址,将name回滚为 赵六

Read View机制

在可重复读隔离级别下,一个事务在开始时,执行任何的查询SQL脚本,都会生成一个属于当前事务自己的 Read View一致性视图 ,这个视图在这个事务结束之间,都是保持不变的(除非在本事务里面自己执行了更新操作)。

如果事务隔离级别是读已提交,则 Read View一致性视图 是在每次执行查询SQL脚本时,都会重新生成,与可重复读隔离级别的在同一事务里保持不变不同。

这个视图是什么呢?

这个视图是由执行查询SQL脚本这一时刻,所有还未提交的事务id构建而成的数组,和此时存在最大的一个事务id共同构建而成。

示意图一

请添加图片描述

示意图二

请添加图片描述

结合上面两图分析可知,

事务A 第一次执行查询语句时刻,所生成的ReadView的构成为 {[1002,1003,1004], 1004} ,其中[1002,1003,1004]为未提交的事务id(min_id=1002,min_id最小事务id是从未提交事务id里获取的),1004为当前最大的事务id(max_id=1004)。

事务A 第二次、三次执行查询语句时刻,其ReadView的依旧为 {[1002,1003,1004], 1004}

事务B 第一次执行查询语句时刻,所生成的ReadView的构成为 {[1002,1004], 1004} ,其中[1002,1004]为未提交的事务id(min_id=1002),1004为当前最大的事务id(max_id=1004)。

事务B 第二次执行查询语句时刻,所生成的ReadView的依旧为 {[1002,1004], 1004}

根据事务A和事务B的ReadView可以得出一个相同的工具图(用来判断某事务的某一次数据更新,是否对select是可见的)

事务里的select语句查询结果都是需要从undo日志版本链最新数据开始,逐条与本事务的ReadView进行比对,判断应该获取到哪一日志版本的数据为select语句的查询结果。

示意图三

请添加图片描述

比对规则

  • 如果比较的undo日志的 trx_id小于min_id ,则表示这个版本事务是已经提交的,代表本次select这一事务可以查到这个数据。

  • 如果比较的undo日志的 trx_id大于max_id ,则表示这个版本事务是在本次select这一事务后面新启动的,这种数据肯定是不可被查询到的。

  • 如果比较的undo日志的 trx_id大于等于min_id ,且 trx_id小于等于max_id ,则再判断 trx_id是否是在未提交事务id数组里。

    • 在未提交事务id数组里,则表示这个版本数据是由未提交的事务所生成的,这种数据不可被查询到(除非这个未提交的事务就是自己)。
    • 不在未提交事务id数组里,则表示这个版本数据是由已经提交的事务所生成的,这种数据可以被查询到。

案例一

那么事务A第一条select语句查询脚本执行时,获取到的name是什么呢?

先说结果,查询到的name为赵六。

事务A执行第一条select语句时,他的SQL执行顺序,和SQL查询脚本执行时拿到的日志版本链及判断示意图如下。

请添加图片描述

请添加图片描述

上面在示意图二里也说了,事务A的第一次执行查询语句时刻时,其ReadView为 {[1002,1003,1004], 1004}

请添加图片描述

比较步骤如下

第一次: 事务A从undo日志版本链拿到最后一次更新记录(第二次修改记录),得到trx_id为1003,然后对照着上面的 判断工具图 进行比较,大于min_id,小于max_id,所以1003事务id属于是 第二部分 ,且1003这一事务id处于还未提交的事务id构建而成的数组里(未提交的事务id数组[1002,1003,1004]),所以这个事务进行的更新数据,是不可被查询到的。

于是根据roll_pinter回滚指针(上一版本数据的地址)找到上一个版本数据。

第二次: 事务A从undo日志版本链拿到第二条日志记录(第一次插入记录),得到trx_id为1001,然后同样对照着上面的 判断工具图 进行比较,发现小于min_id的1002,所以1001事务id属于是 第一部分 ,是已经提交了事务的,他更新的数据就属于可以被查询到,于是此时查询到的结果name为张三。

案例二

那么事务A第二条select语句查询脚本执行时,获取到的name是什么呢?

先说结果,查询到的name依旧为赵六。

事务A执行第二条select语句时,他的SQL执行顺序,和SQL查询脚本执行时拿到的日志版本链及判断示意图如下。

请添加图片描述

请添加图片描述

上面在示意图二里也说了,事务A的第二次和第三次执行查询语句时刻时,其ReadView依旧为 {[1002,1003,1004], 1004}

请添加图片描述

比较步骤如下

第一次: 事务A从undo日志版本链拿到最后一次更新记录(第四次修改记录),得到trx_id为1003,然后对照着上面的 判断工具图 进行比较,大于min_id,小于max_id,所以1003事务id属于是 第二部分 ,继续判断得知,1003这一事务id处于还未提交的事务id构建而成的数组里(未提交的事务id数组[1002,1003,1004])(即便此时事务1003已经提交了,但是只要在事务A第一次执行查询语句时,这个事务没有提交,那他在事务A里就一直被标记的是未提交,否则就会出现 不可重复读 问题),所以这个事务进行的更新数据,是不可被查询到的。

于是根据roll_pinter回滚指针(上一版本数据的地址)找到上一个版本数据。

第二次: 事务A从undo日志版本链拿到第二条日志记录(第三次修改记录),得到trx_id为1002,然后同样对照着上面的 判断工具图 进行比较,发现等于min_id的1002,所以1002事务id属于是 第二部分 ,继续判断得知,1002这一事务id处于还未提交的事务id构建而成的数组里(未提交的事务id数组[1002,1003,1004]),所以这个事务进行的更新数据,是不可被查询到的。

于是根据roll_pinter回滚指针(上一版本数据的地址)找到上一个版本数据。

第三次: 事务A从undo日志版本链拿到最后一次更新记录(第二次修改记录),得到trx_id为1003,然后对照着上面的 判断工具图 进行比较,大于min_id,小于max_id,所以1003事务id属于是 第二部分 ,且1003这一事务id处于还未提交的事务id构建而成的数组里(未提交的事务id数组[1002,1003,1004]),所以这个事务进行的更新数据,是不可被查询到的。

于是根据roll_pinter回滚指针(上一版本数据的地址)找到上一个版本数据。

第四次: 事务A从undo日志版本链拿到第二条日志记录(第一次插入记录),得到trx_id为1001,然后同样对照着上面的 判断工具图 进行比较,发现小于min_id的1002,所以1001事务id属于是 第一部分 ,是已经提交了事务的,他更新的数据就属于可以被查询到,于是此时查询到的结果name为张三。

案例三

那么事务B第一条select语句查询脚本执行时,获取到的name是什么呢?

先说结果,查询到的name为李四。

为什么事务B的一条查询语句和事务A的第二条查询语句是同时执行的,但是结果不一样呢?可以详细看看下面对事务的比较步骤。

事务B 第一次执行查询语句时刻,其所生成的ReadView的构成为 {[1002,1004], 1004} ,其中[1002,1004]为未提交的事务id(min_id=1002),1004为当前最大的事务id(max_id=1004)。

事务B执行第一条select语句时,他的SQL执行顺序,和SQL查询脚本执行时拿到的日志版本链及判断示意图同案例二里的一样。

比较步骤如下

第一次: 事务B从undo日志版本链拿到最后一次更新记录(第四次修改记录),得到trx_id为1003,然后对照着上面的 案例二的判断工具图 进行比较,大于min_id,小于max_id,所以1003事务id属于是 第二部分 ,继续判断得知,1003这一事务id 不处于 还未提交的事务id构建而成的数组里(未提交的事务id数组[1002,1004]),所以表示这个事务已经提交了,这个事务所更新的数据版本可以被查询到,查询结果就是name为李四。

案例四

事务A第三条select语句查询脚本执行时,获取到的name是什么呢?

先说结果,查询到的name还是赵六。

事务A执行第三条select语句时,他的SQL执行顺序,和SQL查询脚本执行时拿到的日志版本链及判断示意图如下。

请添加图片描述

请添加图片描述

上面在示意图二里也说了,事务A的第二次和第三次执行查询语句时刻时,其ReadView依旧为 {[1002,1003,1004], 1004}

请添加图片描述

比较步骤如下

第一次: 事务A从undo日志版本链拿到最后一次更新记录(第五次修改记录),得到trx_id为1004,然后对照着上面的 判断工具图 进行比较,大于min_id,小于 等于 max_id,所以1004事务id属于是 第二部分 ,且1004这一事务id 不处于 还未提交的事务id构建而成的数组里(未提交的事务id数组[1002,1003,1004]),所以这个事务进行的更新数据,是不可被查询到的。

于是根据roll_pinter回滚指针(上一版本数据的地址)找到上一个版本数据。

第二次: 比较过程和示例二里的第一次比较操作过程一样。

于是根据roll_pinter回滚指针(上一版本数据的地址)找到上一个版本数据。

第三次: 比较过程和示例二里的第二次比较操作过程一样。

于是根据roll_pinter回滚指针(上一版本数据的地址)找到上一个版本数据。

第四次: 比较过程和示例二里的第三次比较操作过程一样。

于是根据roll_pinter回滚指针(上一版本数据的地址)找到上一个版本数据。

第五次: 比较过程和示例二里的第四次比较操作过程一样。得到查询结果name为张三。

案例五

那么事务B第二条select语句查询脚本执行时,获取到的name是什么呢?

先说结果,查询到的name为李四。

事务B 第二次执行查询语句时刻,其所生成的ReadView的依旧为 {[1002,1004], 1004} 。和其第一次执行查询语句时刻一样。

事务B执行第三条select语句时,他的SQL执行顺序图,和SQL查询脚本执行时拿到的日志版本链和上面案例三的一样。

虽然ReadView和案例三的不同,为 {[1002,1004], 1004} ,但是其形成的判断图还是一样的。

请添加图片描述

比较步骤如下

第一次: 事务B从undo日志版本链拿到最后一次更新记录(第五次修改记录),得到trx_id为1004,然后对照着上面的 判断工具图 进行比较,大于min_id,小于 等于 max_id,所以1004事务id属于是 第二部分 ,且1004这一事务id 不处于 还未提交的事务id构建而成的数组里(未提交的事务id数组[1002,1004])(即便此时事务1004已经提交了,但是只要在事务B第一次执行查询语句时,这个事务没有提交,那他在事务B里就一直被标记的是未提交,否则就会出现 不可重复读 问题),所以这个事务进行的更新数据,是不可被查询到的。(和上面案例二的第一次比较一样)

第二次:事务B从undo日志版本链拿到第二条日志记录(第四次修改记录),得到trx_id为1003,然后对照着上面的 案例二的判断工具图 进行比较,大于min_id,小于max_id,所以1003事务id属于是 第二部分 ,继续判断得知,1003这一事务id 不处于 还未提交的事务id构建而成的数组里(未提交的事务id数组[1002,1004]),所以表示这个事务已经提交了,这个事务所更新的数据版本可以被查询到,查询结果就是name为李四。

读已提交隔离级别又是怎么比较的

读已提交 (已提交读)事务隔离级别的undo日志版本链和 可重复读 是一样的,Read View的生成规则就不同了, 读已提交的 Read View一致性视图 是在每次执行查询SQL脚本时,都会重新生成 ,与可重复读隔离级别的在同一事务里保持不变的定义不同。

这也就导致每次执行查询脚本时,都会重新构建 还未提交的事务id数组 ,所以只要其他事务在本事务执行这一条查询脚本之前,更新数据并提交了,那他的更新数据就可以被本事务的这一次查询操作查询到。

其他的比对规则还是和 可重复读 事务隔离级别一样,仅仅是ReadView在每次查询时需要重新生成。

可以理解为 读已提交 事务隔离,只会读到最后一次提交了更新操作事务的数据。

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

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

相关文章

C# 实时流转换为m3u8

主要通过FFmpeg 执行命令进行转换 FFmpeg 下载地址 命令行 ffmpeg -i "rtsp://your_rtsp_stream_address" -codec: copy -start_number 0 -hls_time 10 -hls_list_size 12 -f hls "output.m3u8"start_number 设置播放列表中最先播放的索引号,…

形式向好、成本较低、可拓展性较高的名厨亮灶开源了

简介 AI视频监控平台, 是一款功能强大且简单易用的实时算法视频监控系统。愿景在最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,减少企业级应用约 95%的开发成本,在强大视频算法加…

C++_21_模板

模板 简介&#xff1a; 一种用于实现通用编程的机制。 通过使用模板我们可以编写可复用的代码&#xff0c;可以适用于多种数据类型。 C模板的语法使用角括号 < > 来表示泛型类型&#xff0c;并使用关键字 template 来定义和声明模板 概念&#xff1a; c范式编程 特点&…

海外大带宽服务器连接失败怎么办?

在全球化日益加深的今天&#xff0c;海外大带宽服务器已成为企业拓展国际市场、提升业务效率的重要工具。然而&#xff0c;面对复杂多变的网络环境和技术挑战&#xff0c;服务器连接失败的问题时有发生&#xff0c;这不仅影响了企业的正常运营&#xff0c;还可能带来经济损失和…

如何写一个自动化Linux脚本去进行等保测试--引言

#我的师兄喜欢给我的休闲实习生活加活&#xff0c;说是让我在实习期间写一个自动化脚本去进行等保测试。呵呵哒。 怎么办呢&#xff0c;师兄的指令得完成&#xff0c;师兄说让我使用Python完成任务。 设想如下&#xff1a; 1、将Linux指令嵌入到python脚本中 2、调试跑通 …

【简历】25届河南某一本JAVA简历:从头到尾都表现的不懂技术

注&#xff1a;为保证用户信息安全&#xff0c;姓名和学校等信息已经进行同层次变更&#xff0c;内容部分细节也进行了部分隐藏 简历总体说明 今天看一份河南某重点一本大学的Java简历。 校招备战第一法则&#xff1a;必须确定自己的求职层次&#xff0c;是大厂、中厂还是小…

php语言基本语法

HP&#xff08;Hypertext Preprocessor&#xff09;是一种广泛使用的开源服务器端脚本语言&#xff0c;特别适合于Web开发。 它能够嵌入到HTML中&#xff0c;执行动态网页内容。 PHP的一些基本语法元素&#xff1a; 1. 基本结构 PHP代码通常嵌入到HTML中&#xff0c;以<…

OpenHarmony(鸿蒙南向开发)——小型系统芯片移植指南(二)

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——轻量系统芯片移植指南(一) Op…

基于深度学习的图像分类或识别系统(含全套项目+PyQt5界面)

目录 一、项目界面 二、代码实现 1、数据集结构 2、设置需要模型的训练参数和指定数据集路径 3、网络代码 4、训练代码 5、评估代码 6、结果显示 三、项目代码 一、项目界面 二、代码实现 1、数据集结构 每一个文件夹对应一个类别的数据 2、设置需要模型的训练参数和…

09.20 C++对C的扩充以及C++中的封装、SeqList

SeqList.h #ifndef SEQLIST_H #define SEQLIST_H#include <iostream> #include<memory.h> #include<stdlib.h> #include<string.h>using namespace std;//typedef int datatype; //类型重命名 using datatype int;//封装一个顺序表 class Seq…

app抓包 chrome://inspect/#devices

一、前言&#xff1a; 1.首先不支持flutter框架&#xff0c;可支持ionic、taro 2.初次需要翻墙 3.app为debug包&#xff0c;非release 二、具体步骤 1.谷歌浏览器地址&#xff1a;chrome://inspect/#devices qq浏览器地址&#xff1a;qqbrowser://inspect/#devi…

新媒体运营

一、新媒体运营的概念 1.新媒体 2.新媒体运营的五大方向 用户运营 产品运营 。。。 二、新媒体的岗位职责及要求 三、新媒体平台

快速开发与维护:探索 AndroidAnnotations

在移动应用开发的世界中&#xff0c;效率和可维护性是两个至关重要的要素。随着应用功能的不断增长和用户需求的不断变化&#xff0c;开发者们一直在寻找能够提高生产力的工具和框架。今天&#xff0c;我们将深入探讨一个能够帮助开发者实现快速开发和易于维护的框架——Androi…

dgl库安装

此篇文章继续上一篇pytorch已经安装成功的情况 &#xff08;python3.9&#xff0c;pytorch2.2.2&#xff0c;cuda11.8&#xff09; 上一篇pytorch安装教学链接 选择与之匹配的版本 输入下方代码进行测试 import dgl.data dataset dgl.data.CoraGraphDataset() print(‘Numb…

使用宝塔部署项目在win上

项目部署 注意&#xff1a; 前后端部署项目&#xff0c;需要两个域名&#xff08;二级域名&#xff0c;就是主域名结尾的域名&#xff0c;需要在主域名下添加就可以了&#xff09;&#xff0c;前端一个&#xff0c;后端一个 思路&#xff1a;访问域名就会浏览器会加载前端的代…

【Redis入门到精通二】Redis核心数据类型(String,Hash)详解

目录 Redis数据类型 1.String类型 &#xff08;1&#xff09;常见命令 &#xff08;2&#xff09;内部编码 2.Hash类型 &#xff08;1&#xff09;常见命令 &#xff08;2&#xff09;内部编码 Redis数据类型 查阅Redis官方文档可知&#xff0c;Redis提供给用户的核心数据…

【HTML5】html5开篇基础(1)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

代码随想录冲冲冲 Day51 图论Part3

101. 孤岛的总面积 dfs 首先dfs的作用就是在遇到陆地的时候找到所有的周围陆地 对于这道题的dfs 会把所有的链接边缘的陆地变成海洋 这样在全部调整之后 剩下的就是孤岛了 这道题中的dfs的结束条件就是遇到海洋时 遇到每一个陆地就会把面积1&#xff0c;在每一次重新找到…

(Java企业 / 公司项目)点赞业务系统设计-批量查询点赞状态(二)

接着上一篇文章来搞,批量查询点赞状态。这个接口提供给其他的微服务调用所以这里会用到FeignClient 直接上接口 1. 接口信息 这里是查询多个业务的点赞状态,因此请求参数自然是业务id的集合。由于是查询当前用戶的点赞状态,因此无需传递用戶信息。当前用户指的是登录用户 …

ELF文件结构

ELF文件格式的最前部是 ELF文件头&#xff08;ELF Header&#xff09; &#xff0c;包含整个文件的基本属性。然后是各个节&#xff0c;ELF文件中与节有关的结构是 “节表&#xff08;Section Header Table&#xff09;”&#xff0c;节表描述ELF文件包含的所有节的信息。 文件…