MVCC工作原理深入解析
一、事务概述
mysql事务是指一组命令操作,在执行过程中用来保证要么全部成功,要么全部失败。事务是由引擎层面来支持的,MyISM引擎不支持事务,InnoDB引擎支持事务。
事务具有ACID四大特性
原子性(Atomicity):指事务不可分割,要么全部成功,要么全部失败,不可能存在部分成功或部分失败的情况。如果执行某一条语句失败后,将会触发之前所有执行过的语句的回滚,因此靠的是undo log进行回滚。
一致性(Consistency):在事务执行前后,数据的完整性没有遭到破坏。一致性是mysql追求的最终目标,需要数据库层面与应用层面同时来维护。需要先满足原子性、隔离性与持久性,同时也需要应用层面做保障,即在应用层面对数据进行检验。
隔离性(Isolation):事务之前是隔离的,并发执行的事务之间不存在互相影响,mysql通过锁以及MVCC来保证隔离性。
持久性(Durability):事务一旦提交,那么对数据的操作就是永久性的,即使接下来数据库宕机也不会有影响。mysql是通过redo log来实现宕机恢复的,而binlog主要是用来误删恢复与主从复制的。
二、事务并发问题
数据库有读—读操作、读—写操作、写—写操作三种并发场景。三种场景下读写操作和写写操作可能会产生并发安全问题。
读—读操作:多线程同时进行读操作,这种场景下不会产生并发安全问题,不需并发控制。
读—写操作:两个线程在同一时刻分别进行读写操作,这种情况下可能会对数据库产生隔离性问题和出现脏读、幻读、不可重复读的问题。
写—写操作:两个线程同一时刻进行写操作,这种情况下可能会存在数据丢失的问题。
1、脏读
读取到别的事务未提交的数据被称为脏读。
A事务读取到了B事务未提交的数据,若B事务发生错误进行回滚操作,那么事务A读取到的数据就是脏数据。
这种情况常发生在转账场景下,如老板发错工资的情况
时间顺序 | 转账事务 | 查询事务 |
1 | 开始事务 | |
2 | 开始事务 | |
3 | 转账3.9万,未提交 | |
4 | 收入3.9万(涨工资了) | |
5 | 提交事务 | |
6 | 发现转错了,进行事务回滚 | |
7 | 重新转账3.6万 | |
8 | 提交事务 |
分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。
2、幻读(Phantom Reads)
某个事务前后多次读取到的数据总量不一致被称为幻读。
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,称为幻读。
时间顺序 | 事务A | 事务B |
1 | 开始事务 | |
2 | 第一次查询,数据总量100条 | |
3 | 开始事务 | |
4 | 新增100条数据 | |
5 | 提交事务 | |
6 | 第二次查询 数据总量为200条 | |
7 | 按照正确逻辑,事务A前后两次读取到的数据总量应该一致 |
3、不可重复读
某个事务范围内前后多次相同查询条件读取到的数据内容不一致被称为不可重复读。
事务A前后两次相同条件的查询时间跨度比较大,在其之间事务B对数据进行了修改操作,导致事务A第二次查询获取的数据与第一次不同(数据不重复了),称为不可重复读。
时间顺序 | 事务A | 事务B |
1 | 开始事务 | |
2 | 第一次查询 小红语文成绩90 | |
3 | 开始事务 | |
4 | 更改小红语文成绩为95 | |
5 | 第二次查询,小红语文成绩95 | |
6 | 按照正确逻辑,事务A前后两次读取到的数据应该一致 |
三、事务隔离级别
事务隔离级别就是在不同程度上解决脏读、幻读、不可重复读的问题。数据库事务隔离级别有四种,由低到高分别为读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)、串行化(Serializable)。
1、读未提交(Read Uncommitted)
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。在这种隔离级别下所有事务都能读取其他事务未提交的数据。
读取其他事务未提交的数据会造成脏读。因此在此隔离级别下不能解决脏读、幻读和不可重复读。
读未提交可能会产生脏读的现象,那么怎么解决脏读呢?那就是使用读已提交。
2、读已提交(Read Committed)
在这种隔离级别下,所有事务只能读取其他事务已经提交的内容。
能够彻底解决脏读的现象。但在这种隔离级别下,会出现一个事务的前后多次的查询中却返回了不同内容的数据的现象,也就是出现了不可重复读。
这是大多数数据库系统默认的隔离级别,例如Oracle和SQL Server,但mysql不是。
已提交可能会产生不可重复读的现象,我们可以使用可重复读。
3、可重复读(Repeatable Read)
在这种隔离级别下,所有事务前后多次的读取到的数据内容是不变的。
也就是某个事务在执行的过程中,不允许其他事务进行update操作,但允许其他事务进行add操作,造成某个事务前后多次读取到的数据总量不一致的现象,从而产生幻读。
这才是mysql的默认事务隔离级别
可重复读在特殊情况下依然会产生幻读,此时我们可以使用串行化来解决。
4、串行化(Serializable)
在这种隔离级别下,所有的事务顺序执行,所以他们之间不存在冲突,从而能有效地解决脏读、不可重复读和幻读的现象。
但是安全和效率不能兼得,串行化会大大降低数据库的性能,一般不使用这种级别。
通过表格来表示四种不同隔离级别能够处理的问题
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
读未提交 | 否 | 否 | 否 | 否 |
读已提交 | 是 | 否 | 否 | 否 |
可重复读 | 是 | 是 | 否(InnoDB除外) | 否 |
串行化 | 是 | 是 | 是 | 是 |
以上所说的隔离级别及当前级别存在的问题只是一种规范,不同的数据库厂商可以有不同的实现。
例如在mysql的可重复读的级别上,使用临键锁的方式就已经解决了幻读的问题。
四、MVCC工作原理
mysql为了实现以上隔离级别,提出了LBCC(Lock-Based Concurrent Control,基于锁的并发控制)与MVCC(Multi-Version Concurrent Control,基于多版本的并发控制)。
在LBCC中,读写冲突,会使用诸如记录锁、间隙锁与临键锁等锁来实现数据的并发安全,因此读写性能不高。
MVCC是为了解决事务中读-写操作并发安全问题的无锁并发控制技术,是通过数据库记录的隐式字段undo log日志和ReadView来实现的。
1、MVCC解决的问题
1. 并发读—写操作:可以解决读写并发阻塞问题,使读操作不阻塞写操作,写操作不阻塞读操作。
2. 解决脏读、不可重复读、幻读等事务隔离性问题
3. MVCC采用乐观锁方式实现,降低了死锁的概率
2、实现原理
MVCC是通过数据库的undo log版本链和ReadView方式来实现的无锁并发控制的。在InnoDB引擎下读已提交和可重复读隔离级别都是基于MVCC进行并发事务控制。
以下面4个事务为例进行解析MVCC实现原理和执行过程。
事务A、B、C对数据进行修改操作,事务D进行两次查询操作,第一次是在事务B修改后未commit前查询,第二次是在事务C修改后未commit前查询。
2.1 undo_log版本链
undo log
undo log主要用于事务回滚时恢复原来的数据。mysql在执行sql语句时,会将一条逻辑相反的日志保存到undo log中。因此,undo log中记录的也是逻辑日志,主要记录insert、update、delete相关操作的数据。
当sql语句为insert时,会在undo log中记录本次插入的主键id。等事务回滚时,delete此id即可。
当sql语句为update时,会在undo log中记录修改前的数据。等事务回滚时,再执行一次update,得到原来的数据。
当sql语句为delete时,会在undo log中记录删除前的数据。等事务回滚时,insert原来的数据即可。
数据库事务四大特性中的原子性,即事务具有不可分割性,要么全部成功,要么全部失败,其底层就靠undo log实现。在某一步执行失败时,会对之前事务的语句进行回滚。
行隐藏列
在数据库中的每一行上,除了存放真实的数据以外,还存在着3个隐藏列——row_id、trx_id与roll_pointer。
row_id(主键id)
若当前表有整数类型的主键,则row_id就是主键的值。若没有整数类型的主键,则mysql会按照字段顺序选择一个非空的整数类型的唯一索引作为row_id。如果mysql没有找到,则会自动生成一个自动增长的整数作为row_id。
trx_id(事务编号)
当一个事务开始执前,mysql会为这个事务分配一个全局自增的事务id。之后该事务对当前行进行的增、删、改操作时,都会将自己的事务id记录到trx_id中。
roll_pointer(回滚指针)
事务对当前行进行修改时,会将旧数据写入进undo log中,再将新数据写入当前行,且当前行的roll_pointer指向刚生成的undo log,因此可以通过roll_pointer找到该行的前一个版本。
undo log版本链
当一直有事务对该条数据进行修改时,就会一直生成undo log日志,最终会将这些undo log日志形成undo log版本链。
上面的例子事务A、B、C修改id=1088的数据行生成的undo log版本链如下
每一个事务对该行修改时,都会生成一个undo log,用于保存之前的版本,之后再将新版本的roll_pointer指向刚才生成的undo log。因此roll_pointer可以将这些不同版本的undo log串联起来,形成undo log版本链。
2.2 ReadView
ReadView是“快照读”SQL执行时MVCC提取数据的依据。首先需要理解下快照读和当前读。
快照读:就是最普通的Select查询的SQL语句,不包括select... for update 和select... lock in share mode。
当前读:读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。如下操作都是当前读:
- select lock in share mode (共享锁)
- select for update (排他锁)
- update (排他锁)
- insert (排他锁)
- delete (排他锁)
- 串行化事务隔离级别
在mysql中只有在快照读的情况下才会使用MVCC,尤其是在InnoDB引擎RR-可重复读隔离级别下会区分对待,快照读使用的是MVCC进行数据读取,当前读使用的是next-key lock锁(行锁+间隙锁)来实现数据读取。快照读和当前读在RR隔离级别下 对数据提取方式是完全不同的
在事务每一次执行快照读或事务初次执行快照读时,会生成一致性视图ReadView。
ReadView的作用是判断undo log版本链中哪些数据对当前事务可见。
ReadView包含几个重要的参数
m_ids:当前活跃的事务编号集合
min_trx_id:最小活跃事务编号(最小未提交事务编号)
max_trx_id:预分配事务编号,当前最大事务编号+1
create_trx_id:ReadView创建者的事务编号
ReadView生成
在RC-读已提交和RR-可重复读隔离级别下生成ReadView方式是不同的。
RC-读已提交
在RC-读已提交隔离级别下,每一次执行快照读时都会生成一个新的ReadView。
在RC隔离级别下,根据上面例子的事务执行为例,生成的ReadView过程
RR-可重复读
在RR-可重复读隔离级别下,仅在第一次执行快照读时生成ReadView,后续快照读复用第一次生成的ReadView。
在RR隔离级别下,根据上面例子的事务执行为例,生成的ReadView过程
ReadView数据提取过程
事务在执行快照读时,会从undo log历史版本链中从最新版本开始往前比对,通过一系列的规则,根据快照版本中的trx_id字段和ReadView来确定该版本对于当前事务是否可见。具体规则如下:
a. 若trx_id < min_trx_id,说明该版本对于当前事务(ReadView)来说,是已提交事务生成的,那么对于当前事务是可见的。
b. 若trx_id >= max_trx_id,说明该版本对于当前事务(ReadView)来说,是“将来”的事务生成的,那么对于当前事务是不可见的。
c. 若min_trx_id<= trx_id < max_trx_id,需进一步判断trx_id是否在m_ids集合列表中
i. 若 trx_id在m_ids中,说明该版本是由还未提交的事务生成的,对于当前事务不可见。
ii. 若trx_id不在m_ids中,说明该版本是已提交的事务生成的,对于当前事务可见。
d. 若trx_id=create_trx_id,即当前对比版本是由当前事务生成的,那么此版本对于当前事务当然可见。
e. 如果当前对比版本不可见,则通过roll_pointer找到上一个版本进行对比,直到找到可见版本或找不到任何一个可见版本。
RC-读已提交
根据上面执行的例子,RC隔离级别下生成了两个不同的ReadView。在比对数据时,两个ReadView中的m_ids不同,最终就会出现两个结果。
ReadView1与undo log比对结果
ReadView2与undo log比对结果
通过上面两个比对结果可知,两次快照读发生了不可重复读的问题,故在RC隔离级别下是无法解决不可重复读的问题。
RR-可重复读
RR-可重复读隔离级别下只生成一次ReadView,后续快照读都是复用之前的ReadView,所以通过ReadView复用的方式就可以解决不可重复读的情况。
RR-可重复读隔离级别下正常情况下通过MVCC版本控制的方式可以解决幻读的问题。因为作为ReadView在没有发生变化的前提下,mysql在中间执行过程中,无论是产生了新增、修改还是删除的话对于事务4都是不可见的。因此在连续多次快照读情况下ReadView会产生复用,没有幻读问题。
但当两次快照读之间存在当前读,ReadView就会重新生成,导致产生幻读。举例如下
事务B先进行查询,获取ReadView1 显示1088-张三。
事务A 插入数据张三,同名不同人,此时事务1对应的undo_log为两条。
接着事务B执行update操作,当前读后再进行快照读是生成了ReadView2,但ReadView1和ReadView2的提取数据不同了。这种情况下在同一个事务B中前后两次相同的select查询结果不同的,产生了幻读。
产生幻读的问题就是在同一事务中 前后两次快照读中间加入了当前读的操作导致出现了幻读。
五、总结
从以上的分析中我们可以看出来,MVCC指的就是在使用RC-读已提交、RR-可重复读这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。
相关文章:

MVCC工作原理深入解析
一、事务概述 mysql事务是指一组命令操作,在执行过程中用来保证要么全部成功,要么全部失败。事务是由引擎层面来支持的,MyISM引擎不支持事务,InnoDB引擎支持事务。 事务具有ACID四大特性 原子性(Atomicity࿰…...

使用html+css+js实现完整的登录注册页面
在这篇博客中,我们将讨论如何使用简单的 HTML 和 CSS 构建一个登录与注册页面。这个页面包含两个主要部分:登录界面和注册界面。我们还会展示如何通过 JavaScript 切换这两个部分的显示状态。 页面结构 我们将创建一个页面,其中包含两个主要…...

2024年8月16日(运维自动化 ansible)
一、回顾 1、mysql和python (1)mysql5.7 1.1不需要执行mysql_ssl_rsa_setup 1.2change_master_to 不需要get public key (2)可以使用pymysql非交互的管理mysql 2.1pymysql.connect(host,user,password,database,port) 2.2 cursorconn.cursor() 2.3 cursor.execute("creat…...

荣耀Magicbook x14 扩容1TB固态
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ 固态硬盘规格 在官网查看加装固态硬盘的接口规格 https://www.honor.com/cn/laptops/honor-magicbook-x14-2023/ https://club.honor.com/cn/thread-2847379…...
Springboot整合全文检索引擎Lucene
文章目录 前言Lucene的介绍springboot项目中如何整合Lucene简单用法1. 引入依赖2. 其它用到的类2. 创建索引3. 简单搜索4. 更新索引5. 删除索引6. 删除全部索引 Springboot整合Lucene复杂搜索1. 同时标题和内容中查找关键词2. 搜索结果高亮显示关键词3. 分页搜索4. 多关键词联合…...
【深度学习】【语音】TTS, 如何使用Python分析WAV的采样率、比特深度、通道数
文章目录 使用Python分析WAV文件的属性与可视化简介所需环境代码解析可视化音频数据结论使用Python分析WAV文件的属性与可视化 WAV文件录音要求 为了确保录制的音频文件符合TTS模型训练的质量标准,请遵循以下录音要求: 采样率要求:44.1 kHz说明:采样率44.1 kHz(即每秒采样…...

Linux的安装和使用
Linux 第一节 Linux 优势 1. 开源 为什么这么多的的设备都选择使用 Linux?因为它是开源软件(open source software),具有不同的含义。使用一个安全的操作系统工作变得必不可少的事,而 Linux 恰好满足了这个需求。因…...

查看一个exe\dll文件的依赖项
方法 使用一个Dependencies工具,检测exe文件的所有依赖项 工具使用 下载压缩包之后解压,解压后如下图所示 在命令行中运行Dependencies.exe程序会得到帮助菜单 查询某exe的所有依赖项,使用命令 Dependencies.exe -chain <查询文件> …...

高校科研信息管理系统pf
TOC springboot364高校科研信息管理系统pf 第1章 绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展,用了短短的几十年时间就风靡全球,使得全球各个行业都进行了互联网的改造升级,标志着互联网浪潮的来临。在这个新的时代,…...

Linux 开机自动挂载共享文件设置
选择一个要共享的文件 点击确定 -> 确定 启动虚拟机 执行下面的命令 /YumSource 是我选择的共享文件夹,自行替换自已选择的文件夹 mkdir -p /mnt/hgfs cat >> /etc/fstab << EOF .host:/YumSource /mnt/hgfs fuse.vmhgfs-fuse allow_other defaul…...
c_cpp_properties.json、launch.json、 tasks.json
在 Visual Studio Code 中,c_cpp_properties.json、launch.json 和 tasks.json 是三个重要的配置文件,它们的作用如下: c_cpp_properties.json: 这个文件用于配置 C/C 扩展的 IntelliSense、编译器路径和包括路径等。它帮助 VS Co…...

mysql 一些知识点 面试用
mysql 1、4个隔离级别与3个现象2、快照读与当前读2.1 可重复读的情况下出现幻读问题的两种情况 3 数据库 常用引擎4、InnoDB存储引擎对MVCC的实现5、索引(重点)5.1 什么是索引5.2 索引的创建与删除5.2.1 查看表中有哪些索引5.2.2 添加索引5.2.3 删除索引 5.3 索引的分类5.4 树数…...

STM32之点亮LED灯
使用固件库实现LED点灯 LED灯: LED灯,是一种能够将电能转化为可见光的半导体器件 控制LED灯: LED灯的正极接到了3.3V,LED灯的负极接到了PA1,也就是GPIOA1引脚 只需要控制PA1为相对应的低电平,即可点亮对…...
Java 多线程练习2 (抽奖比较Runnable写法)
MultiProcessingExercise2 package MultiProcessingExercise120240814;import java.util.ArrayList; import java.util.Collections;public class MultiProcessingExercise1 {public static void main(String[] args) {// 需求:// 在此次抽奖过程中,抽奖…...

使用fastboot更新部分系统
使用fastboot更新部分系统 获取分区信息 > part list sunxi_flash 0Partition Map for UNKNOWN device 0 -- Partition Type: EFIPart Start LBA End LBA NameAttributesType GUIDPartition GUID1 0x00008000 0x000097c5 "boot-r…...
windows 加载portch遇到的错误
import torch 遇到如下错误 File "<stdin>", line 1, in <module> File "C:\Users\Administrator\AppData\Local\Programs\Python\Python311\Lib\site-packages\torch\__init__.py", line 148, in <module> raise err OSError: [W…...

如何将 CICD 模版重构为 CICD component?
极狐GitLab 是 GitLab 在中国的发行版,专门面向中国程序员和企业提供企业级一体化 DevOps 平台,用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规,而且所有的操作都是在一个平台上进行,省事省心省钱。可以一键安装极狐GitL…...

数学建模——评价决策类算法(层次分析法、Topsis)
一、层次分析法 概念原理 通过相互比较确定各准则对于目标的权重, 及各方案对于每一准则的权重,这些权重在人的思维过程中通常是定性的, 而在层次分析法中则要给出得到权重的定量方法. 将方案层对准则层的权重及准则层对目标层的权重进行综合, 最终确定方案层对目标…...

KEEPALIVED 全csdn最详细----理论+实验(干货扎实,包教会的)
环境准备 主机名IP虚拟IP(VIP)功能ka1172.25.254.10172.25.254.100keepalived服务ka2172.25.254.20172.25.254.100keepalived服务realserver1172.25.254.110web服务realserver2172.25.254.120web服务 注意一定要关闭selinux,和防火墙,不然在…...

微信云开发云存储全部下载
一、安装 首先按照这个按照好依赖 安装 | 云开发 CloudBase - 一站式后端云服务 npm i -g cloudbase/cli 二、登录 tcb login 下载 首先在你要下载到的本地文件内创建一个名为:cloudbaserc.json 的json文件。 填入你的id {"envId":"你的云开发环…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...