事务并发控制之说透mvcc

在这里插入图片描述

前言

不知道有没有人有过这样的想法💡,为什么在MySQL中已经有了各种各样的锁了,还需要mvcc呢?如果你没有想过这个问题,那只能证明你真的没有想过。

但是我的建议是可以去想一下,如果你从来没有想过这个问题的话,因为这个问题还挺重要的!思考清楚这个问题,将更有利于我们深入了解MySQL!

接下来我将通过这篇文章向你讲述MVCC中各个方面的知识,保证让你一次看的爽,看的明白!从而更好的理解我开头提出来的问题!

扫盲

MVCC (Multi-Version Concurrency Control) 是一种数据库并发控制的技术。它用于解决多个事务同时访问数据库时可能发生的数据不一致性和并发冲突的问题。
具体来说就是:MVCC意图解决读写锁造成的多个、长时间的读操作饿死写操作问题。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样
在做查询的时候就不用等待另一个事务释放锁。

举个列子,现在有两个事务(可以理解为两个客户端)

事务一:

BEGIN SELECT * FROM `subject`  WHERE id=1.....(此处省略一万行)
SELECT * FROM `subject`  WHERE id=999999COMMIT;

事务二:

BEGIN UPDATE `subject` SET `name`='ccc121121' WHERE id =1COMMIT;

事务一有很多个查询需要做,而事务二只有一个更新需要做,假设事务一需要耗费很长时间,如果一定要等到第一个事务执行完成以后才能进行第二个事务的话,那用户的体验将会非常差,所以为了解决个问题才
提出了mvcc,mvcc的存在使得 ** 你可以读我可以写!**

事务到底是啥?

事务是一个或多个 SQL 语句组成的一个执行单元,这些 SQL 语句要么全部执行成功,要么全部不执行,不会出现部分执行的情况。 事务是数据库管理系统执行过程中的一个逻辑单位,由有限的数据库操作序列构成。事务的主要作用是保证数据库操作的一致性,即事务内的操作,要么全部成功,要么全部失败回滚,不会出现中间状态。这对于维护数据库的完整性和一致性非常重要。

事务具有四个基本特性,也就是通常所说的 ACID 特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

那么这些事务的特性该如何保证呢?

从数据库层面上说,一致性是最终目的,数据库通过原子性、隔离性、持久性来实现数据的一致性。

但是,如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如,你在转账的例子中,你的代码里故意不给B账户加钱,那一致性还是无法保证。

因此,还必须从应用层角度考虑

原子性:

Innodb中的undo log可以是实现原子性的关键,当事务回滚时会撤销所有已经执行完毕的sql语句,但是需要记录回滚的日志信息。

例如:

(1)当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据(2)当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作(3)当你insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作

undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。

持久性:

innodb中的redo log可以保证持久性。Mysql是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。

redo log解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在redo log中记录这次操作。当事务提交的时候,会将redo log日志进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据undo log和bin log内容决定回滚数据还是提交数据。

隔离性:

Mysql利用锁和MVCC多版本并发控制(Multi Version Concurrency Control)来保证隔离性。一个行记录数据有多个版本对快照数据,这些快照数据在undo log中。

如果一个事务读取的行正在做DELELE或者UPDATE操作,读取操作不会等行上的锁释放,而是读取该行的快照版本

但是有一点说明一下,在事务隔离级别为读已提交(Read Commited)时,一个事务能够读到另一个事务已经提交的数据,是不满足隔离性的。但是当事务隔离级别为可重复读(Repeateable Read)中,是满足隔离性的。

说到隔离性我们不得不说一下事务的隔离级别

事务的隔离级别与并发问题

事务的并发问题有哪些

先看一下访问相同数据的事务在不保证串行执行 (也
就是执行完一个再执行另一个)的情况下可能会出现哪些问题:

  1. 脏写( Dirty Write )
    对于两个事务 Session A、Session B,如果事务Session A 修改了 另一个 未提交 事务Session B 修改过 的数
    据,那就意味着发生了 脏写

  2. 脏读( Dirty Read )
    对于两个事务 Session A、Session B,Session A读取了已经被 Session B更新但还没有被提交的字段。
    之后若 Session B 回滚 ,Session A 读取的内容就是临时且无效的。
    Session A和Session B各开启了一个事务,Session B中的事务先将studentno列为1的记录的name列更新
    为’张三’,然后Session A中的事务再去查询这条studentno为1的记录,如果读到列name的值为’张三’,而
    Session B中的事务稍后进行了回滚,那么Session A中的事务相当于读到了一个不存在的数据,这种现象
    就称之为 脏读 。

  3. 不可重复读( Non-Repeatable Read )
    对于两个事务Session A、Session B,Session A 读取 了一个字段,然后 Session B 更新 了该字段。 之后
    Session A 再次读取 同一个字段, 值就不同 了。那就意味着发生了不可重复读。
    我们在Session B中提交了几个 隐式事务 (注意是隐式事务,意味着语句结束事务就提交了),这些事务
    都修改了studentno列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看
    到最新的值,这种现象也被称之为 不可重复读 。

  4. 幻读( Phantom )
    对于两个事务Session A、Session B, Session A 从一个表中 读取 了一个字段, 然后 Session B 在该表中 插
    入 了一些新的行。 之后, 如果 Session A 再次读取 同一个表, 就会多出几行。那就意味着发生了幻读。
    Session A中的事务先根据条件 studentno > 0这个条件查询表student,得到了name列值为’张三’的记录;
    之后Session B中提交了一个 隐式事务 ,该事务向表student中插入了一条新记录;之后Session A中的事务
    再根据相同的条件 studentno > 0查询表student,得到的结果集中包含Session B中的事务新插入的那条记
    录,这种现象也被称之为 幻读 。我们把新插入的那些记录称之为 幻影记录 。

SQL中的四种隔离级别

为了解决上述存在的并发问题,相应的SQL中也给出了四种对应隔离级别:

  • READUNCOMMITTED:读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。
  • READCOMMITTED:读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。
  • REPEATABLEREAD:可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍然存在。这是MySQL的默认隔离级别。
  • SERIALIZABLE:可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。

无论师并发问题,还是四种隔离级别他们都是因为事务的并发操作而产生的,说到这里我们得先说一下什么数据库事务的并发操作有!

MySQL并发事务访问相同记录

并发事务访问相同记录的情况大致可以划分为3种

  • 读-读
  • 写-写
  • 读-写

读-读 情况,即并发事务相继读取相同的记录。读取操作本身不会对记录有任何影响,并不会引起什么
问题,所以允许这种情况的发生。

写-写 情况,即并发事务相继对相同的记录做出改动。在这种情况下会发生 脏写 的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务
相继对一条记录做改动时,需要让它们 排队执行 ,这个排队的过程其实是通过 锁 来实现的。

读-写 或 写-读 ,即一个事务进行读取操作,另一个进行改动操作。这种情况下可能发生 脏读 、不可重
复读 、 幻读 的问题。

我们现在也知道读-写这种并发操作会带来很多的问题,出问题就要解决问题呀,对吧!活着的意义就是不断发现问题然后解决问题,最后死去!
在解决问题之前我们要先了解一下什么是"当前读"什么又是"快照读"

什么是当前读和快照读?

当前读它读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。如下操作都是当前读:

  • selectlockin share mode(共享锁)
  • select forupdate (排他锁)
  • update (排他锁)
  • insert (排他锁)
  • delete (排他锁)

如果不了解锁相关的东西可以看我之前写的一篇《【面试题】细说mysql中的各种锁》的文章,里面讲的很详细!

快照读的实现是基于多版本并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。
如下操作是快照读:

  • 不加锁的select操作(注:事务级别不是串行化,也就是平时经常使select查询语句)

所以有没有发现其实快照读就是体现mvcc的一种方式!

所以到现在我们可以回答开篇的问题了,锁和mvcc是共存的,运用的场景不同,目的就是为了提高数据的并发性!

MVCC实现原理

MVCC的实现依赖于:隐藏字段、Undo Log、Read View。

什么是隐藏列?

对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列。

  • trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的 事务id 赋值给trx_id 隐藏列。
  • roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

undo log

前面我们已经提到了,Mysql利用锁和MVCC多版本并发控制(Multi Version Concurrency Control)来保证隔离性。一个行记录数据有多个版本对快照数据,这些快照数据在undo log中。

什么是Read View呢

直接翻译就是:读视图,在多个事务,不同隔离级别下数据有多个版本,那在不同的事务中该如何展示呢?
Read View就是为了解决这一问题!

使用 READ UNCOMMITTED 隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录
的最新版本就好了。
使用 SERIALIZABLE 隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。
使用 READ COMMITTED 和 REPEATABLE READ 隔离级别的事务,都必须保证读到 已经提交了的 事务修改
过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问
题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。

这个ReadView中主要包含4个比较重要的内容,分别如下:

  1. creator_trx_id ,创建这个 Read View 的事务 ID。

说明:只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为
事务分配事务id,否则在一个只读事务中的事务id值都默认为0。

  1. trx_ids ,表示在生成ReadView时当前系统中活跃的读写事务的 事务id列表 。

  2. up_limit_id ,活跃的事务中最小的事务 ID。

  3. low_limit_id ,表示生成ReadView时系统中应该分配给下一个事务的 id 值。low_limit_id 是系
    统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。

注意:low_limit_id并不是trx_ids中的最大值,事务id是递增分配的。比如,现在有id为1,
2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,
trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是4。

ReadView的规则

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见。

如果被访问版本的trx_id属性值与ReadView中的 creator_trx_id 值相同,意味着当前事务在访问
它自己修改过的记录,所以该版本可以被当前事务访问。

如果被访问版本的trx_id属性值小于ReadView中的 up_limit_id 值,表明生成该版本的事务在当前
事务生成ReadView前已经提交,所以该版本可以被当前事务访问。

如果被访问版本的trx_id属性值大于或等于ReadView中的 low_limit_id 值,表明生成该版本的事
务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。

如果被访问版本的trx_id属性值在ReadView的 up_limit_id 和 low_limit_id 之间,那就需要判
断一下trx_id属性值是不是在 trx_ids 列表中。

如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。

如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

MVCC整体操作流程

  1. 首先获取事务自己的版本号,也就是事务 ID;
  2. 获取 ReadView;
  3. 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;
  4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
  5. 最后返回符合规则的数据。
    在这里插入图片描述

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

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

相关文章

【MyBatisPlus】一、公共字段填充配置

目录 一、实体类配置 二、配置MyBatis Plus元对象处理器 三、接口字段自动填充 在使用mybatisplus项目中设置公共字段填充,可以按如下进行配置 一、实体类配置 TableField(value "create_time",fill FieldFill.INSERT)private LocalDateTime createTime…

StarRocks x Paimon 构建极速实时湖仓分析架构实践

Paimon 介绍 Apache Paimon 是新一代的湖格式,可以使用 Flink 和 Spark 构建实时 Lakehouse 架构,以进行流式处理和批处理操作。Paimon 创新性地使用 LSM(日志结构合并树)结构,将实时流式更新引入 Lakehouse 架构中。 …

AM解调 FPGA(寻找复刻电赛电赛D题的)

设计平台 Quartus II10.3mif产生工具modelsimSE (仿真用) DDS(直接数字式频率合成器) 从前面的内容可知,我们需要产生一个载波,并且在仿真时,我们还需要一个较低频率的正弦波信号来充当我们的…

c++图论基础(1)

目录 无向图 无向图度 无向图性质 有向图 有向图度 有向图性质 图的分类: 稀疏图: 稠密图: 零图: 有向完全图: 无向完全图: 度序列: 图是由顶点集合(简称点集)和顶点间的边(简称边…

JSON六种值类型的写法

JSON(JavaScript Object Notation)是一种人类可读的文本数据格式。它源于JavaScript,标准开放,格式要求更为严格,独立于具体编程语言,常用于数据交换。 列举一段JSON数据,解释JSON六种值类型的…

嵌入式开发一:初识Stm32

目录 一、嵌入式简介 1.1 嵌入式概念 1.2 嵌入式系统的组成 1.3 嵌入式的分类 1.3.1 嵌入式系统的分类 1.3.2 嵌入式处理器的分类 二、单片机简介(MCU嵌入式微控制器) 2.1 单片机是什么 2.2 单片机的作用是什么 2.3 单片机的发展历程 2.4 单片机发展趋势 2.5 复杂指…

HP Pavilion Plus Laptop 16-ab0040TU原厂Win11系统

惠普HP星16-abxxxx笔记本电脑原装出厂Windows11系统镜像安装包下载,恢复出厂开箱状态预装OEM系统 适用型号: 16-ab0011TU、16-ab0040TU、16-ab0041TU、16-ab0042TU、16-ab0043TU 16-ab0044TX、16-ab0045TX、16-ab0046TX、16-ab0047TX 链接&#xff1a…

mac资源库的东西可以删除吗?提升Mac运行速度秘籍 Mac实用软件

很多小伙伴在使用mac电脑处理工作的时候,就会很疑惑,电脑的运行速度怎么越来越慢,就想着通过删除mac资源库的东西,那么mac资源库的东西可以删除吗?删除了会不会造成电脑故障呢? 首先,mac资源库…

Android使用ProtoBuf 适配 gradle7.5 gradle8.0

ProtoBuf 适配 Gradle7.5 gradle-wrapper.properties 配置 distributionUrlhttps\://services.gradle.org/distributions/gradle-7.5-bin.zipProject:build.gradle: plugins {id com.android.application version 7.4.2 apply falseid com.android.library versio…

Unreal Engine子类化系统UButton

UE系统Button点击事件无法传递参数,通过子类化系统Button添加自定义参数扩展实现Button点击事件参数传递点击C类文件夹,在右边的区域点击鼠标右键,在弹出的菜单中选择“新建C类”在弹出的菜单中选中“显示所有类”,选择Button作为…

Vscode上使用Clang,MSVC, MinGW, (Release, Debug)开发c++完全配置教程(包含常见错误),不断更新中.....

1.VSCode报错头文件找不到 clang(pp_file_not_found) 在Fallback Flags中添加 -I(是-include的意思,链接你的编译器对应头文件地址,比如我下面的是MSVC的地址) 问题得到解决~

uniapp-css多颜色渐变:左右+上下

案例展示 案例代码&#xff1a; 代码灵感&#xff1a;使用伪类进行处理 <view class"headBox"></view>.headBox {height: 200rpx;background: linear-gradient(to right, #D3D5F0, #F0DCF3, #F7F6FB, #DAE8F2, #E1D3EE);position: relative; }.headBox…

【论文速读】|理解基于大语言模型的模糊测试驱动程序生成

本次分享论文&#xff1a;Understanding Large Language Model Based Fuzz Driver Generation 基本信息 原文作者&#xff1a;Cen Zhang, Mingqiang Bai, Yaowen Zheng, Yeting Li, Xiaofei Xie, Yuekang Li, Wei Ma, Limin Sun, Yang Liu 作者单位&#xff1a;南洋理工大学…

13.电子产品拆解分析-插排带3USB

13.电子产品拆解分析-插排带3USB 一、功能介绍二、电路分析以及器件作用1、三个插座之间通过电线连接,总开关控制火线2、通过FSD3773低待机功耗原边反馈AC/DC驱动芯片控制5V的输出一、功能介绍 ①一键控制总电源开关;②带三路USB输出;③最大支持2500W输出,10A输出电流;④8…

排序算法-计数排序

一、计数排序 这种排序算法 是利用数组下标来确定元素的正确位置的。 如果数组中有20个随机整数&#xff0c;取值范围为0~10&#xff0c;要求用最快的速度把这20个整数从小到大进行排序。 很大的情况下&#xff0c;它的性能甚至快过那些时间复杂度为O(nlogn&#xff09;的排序。…

汽车底盘域的学习笔记

前言&#xff1a;底盘域分为传统车型底盘域和新能源车型底盘域&#xff08;新能源系统又可以分为纯电和混动车型&#xff0c;有时间可以再研究一下&#xff09; 1&#xff1a;传统车型底盘域 细分的话可以分为四个子系统 传动系统 行驶系统 转向系统 制动系统 1.1传动系…

Golang | Leetcode Golang题解之第52题N皇后II

题目&#xff1a; 题解&#xff1a; func totalNQueens(n int) (ans int) {columns : make([]bool, n) // 列上是否有皇后diagonals1 : make([]bool, 2*n-1) // 左上到右下是否有皇后diagonals2 : make([]bool, 2*n-1) // 右上到左下是否有皇后var backtrack func(int)…

git如何查询回退之前的提交记录

git如何查询回退之前的提交记录 使用 git reflog 命令&#xff1a; 使用 git reflog 命令&#xff1a; git refloggit reflog 显示的是你的本地引用日志&#xff0c;它包含了所有HEAD指向变更的历史记录&#xff0c;即使那些已经被删除的提交也会出现在这里。当你误操作回退并…

【QA】Git的底层原理

前言 本文通过一个简单的示例&#xff0c;来理解Git的底层原理。 示例 1、新建本地仓库并上传第一个文件 相关步骤&#xff1a; 新建仓库及创建文件查看文件状态将文件添加到暂存区将文件提交到本地仓库 HMTeenLAPTOP-46U4TV6K MINGW64 /d/GSF_Data/Github/Java/Git/git-…

国产麒麟v10系统下打包electron+vue程序,报错unknown output format set

报错如下&#xff1a; 报错第一时间想到可能是代码配置原因报错&#xff0c;查看代码似乎感觉没啥问题 又查看具体报错原因可能是因为icon的原因报错&#xff0c;后面查阅发现ico在各系统平台会不兼容&#xff0c;也就是ico是给win下使用的&#xff0c;此处改下图标格式就ok&am…