【黄啊码】MySQL入门—14、细说数据库的MVCC机制

news2024/12/21 18:58:44

大家好,我是黄啊码。上一篇文章中,我们讲到了锁的划分,以及乐观锁和悲观锁的思想。今天我们就来看下 MVCC,它就是采用乐观锁思想的一种方式。那么它到底有什么用呢?

我们知道事务有 4 个隔离级别,以及可能存在的三种异常问题,如下图所示:

在 MySQL 中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,但不能解决幻读问题。如果我们想要解决幻读问题,就需要采用串行化的方式,也就是将隔离级别提升到最高,但这样一来就会大幅降低数据库的事务并发能力。

有没有一种方式,可以不采用锁机制,而是通过乐观锁的方式来解决不可重复读和幻读问题呢?实际上 MVCC 机制的设计,就是用来解决这个问题的,它可以在大多数情况下替代行级锁,降低系统的开销。

MVCC 是什么,解决了什么问题

MVCC 的英文全称是 Multiversion Concurrency Control,中文翻译过来就是多版本并发控制技术。从名字中也能看出来,MVCC 是通过数据行的多个版本管理来实现数据库的并发控制,简单来说它的思想就是保存数据的历史版本。这样我们就可以通过比较版本号决定数据是否显示出来(具体的规则后面会介绍到),读取数据的时候不需要加锁也可以保证事务的隔离效果。

通过 MVCC 我们可以解决以下几个问题:

  1. 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
  2. 降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
  3. 解决一致性读的问题。一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。

什么是快照读,什么是当前读

那么什么是快照读呢?快照读读取的是快照数据。不加锁的简单的 SELECT 都属于快照读,比如这样:

SELECT * FROM player WHERE ...

当前读就是读取最新数据,而不是历史版本的数据。加锁的 SELECT,或者对数据进行增删改都会进行当前读,比如: 

SELECT * FROM player LOCK IN SHARE MODE;
SELECT * FROM player FOR UPDATE;
INSERT INTO player values ...
DELETE FROM player WHERE ...
UPDATE player SET ...

这里需要说明的是,快照读就是普通的读操作,而当前读包括了加锁的读取和 DML 操作。

上面讲 MVCC 的作用,你可能觉得有些抽象。我们用具体的例子体会一下。

比如我们有个账户金额表 user_balance,包括三个字段,分别是 username 用户名、balance 余额和 bankcard 卡号,具体的数据示意如下:

为了方便,我们假设 user_balance 表中只有用户 A 和 B 有余额,其他人的账户余额均为 0。下面我们考虑一个使用场景。

用户 A 和用户 B 之间进行转账,此时数据库管理员想要查询 user_balance 表中的总金额:

SELECT SUM(balance) FROM user_balance

可以思考下,如果数据库不支持 MVCC 机制,而是采用自身的锁机制来实现的话,可能会出现怎样的情况呢?

情况 1:因为需要采用加行锁的方式,用户 A 给 B 转账时间等待很久,如下图所示。

 你能看到为了保证数据的一致性,我们需要给统计到的数据行都加上行锁。这时如果 A 所在的数据行加上了行锁,就不能给 B 转账了,只能等到所有操作完成之后,释放行锁再继续进行转账,这样就会造成用户事务处理的等待时间过长。

情况 2:当我们读取的时候用了加行锁,可能会出现死锁的情况,如下图所示。比如当我们读到 A 有 1000 元的时候,此时 B 开始执行给 A 转账:

UPDATE user_balance SET balance=balance-100 WHERE username ='B'

执行完之后马上执行下一步:

UPDATE user_balance SET balance=balance+100 WHERE username ='A'

我们会发现此时 A 被锁住了,而管理员事务还需要对 B 进行访问,但 B 被用户事务锁住了,此时就发生了死锁。

MVCC 可以解决读写互相阻塞的问题,这样提升了效率,同时因为采用了乐观锁的思想,降低了死锁的概率。

InnoDB 中的 MVCC 是如何实现的?

我刚才讲解了 MVCC 的思想和作用,实际上 MVCC 没有正式的标准,所以在不同的 DBMS 中,MVCC 的实现方式可能是不同的,你可以参考相关的 DBMS 文档。今天我来讲一下 InnoDB 中 MVCC 的实现机制。

在了解 InnoDB 中 MVCC 的实现方式之前,我们需要了解 InnoDB 是如何存储记录的多个版本的。这里的多版本对应的就是 MVCC 前两个字母的释义:Multi Version,我们需要了解和它相关的数据都有哪些,存储在哪里。这些数据包括事务版本号、行记录中的隐藏列和 Undo Log。

事务版本号

每开启一个事务,我们都会从数据库中获得一个事务 ID(也就是事务版本号),这个事务 ID 是自增长的,通过 ID 大小,我们就可以判断事务的时间顺序。

行记录的隐藏列

InnoDB 的叶子段存储了数据页,数据页中保存了行记录,而在行记录中有一些重要的隐藏字段,如下图所示:

  1. db_row_id:隐藏的行 ID,用来生成默认聚集索引。如果我们创建数据表的时候没有指定聚集索引,这时 InnoDB 就会用这个隐藏 ID 来创建聚集索引。采用聚集索引的方式可以提升数据的查找效率。
  2. db_trx_id:操作这个数据的事务 ID,也就是最后一个对该数据进行插入或更新的事务 ID。
  3. db_roll_ptr:回滚指针,也就是指向这个记录的 Undo Log 信息。

Undo Log

InnoDB 将行记录快照保存在了 Undo Log 里,我们可以在回滚段中找到它们,如下图所示:

从图中你能看到回滚指针将数据行的所有快照记录都通过链表的结构串联了起来,每个快照的记录都保存了当时的 db_trx_id,也是那个时间点操作这个数据的事务 ID。这样如果我们想要找历史快照,就可以通过遍历回滚指针的方式进行查找。

Read View 是如何工作的

在 MVCC 机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在 Undo Log 里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到 Read View 了,它帮我们解决了行的可见性问题。Read View 保存了当前事务开启时所有活跃(还没有提交)的事务列表,换个角度你可以理解为 Read View 保存了不应该让这个事务看到的其他的事务 ID 列表。

在 Read VIew 中有几个重要的属性:

  1. trx_ids,系统当前正在活跃的事务 ID 集合。
  2. low_limit_id,活跃的事务中最大的事务 ID。
  3. up_limit_id,活跃的事务中最小的事务 ID。
  4. creator_trx_id,创建这个 Read View 的事务 ID。

如图所示,trx_ids 为 trx2、trx3、trx5 和 trx8 的集合,活跃的最大事务 ID(low_limit_id)为 trx8,活跃的最小事务 ID(up_limit_id)为 trx2。

假设当前有事务 creator_trx_id 想要读取某个行记录,这个行记录的事务 ID 为 trx_id,那么会出现以下几种情况。

如果 trx_id < 活跃的最小事务 ID(up_limit_id),也就是说这个行记录在这些活跃的事务创建之前就已经提交了,那么这个行记录对该事务是可见的。

如果 trx_id > 活跃的最大事务 ID(low_limit_id),这说明该行记录在这些活跃的事务创建之后才创建,那么这个行记录对当前事务不可见。

如果 up_limit_id < trx_id < low_limit_id,说明该行记录所在的事务 trx_id 在目前 creator_trx_id 这个事务创建的时候,可能还处于活跃的状态,因此我们需要在 trx_ids 集合中进行遍历,如果 trx_id 存在于 trx_ids 集合中,证明这个事务 trx_id 还处于活跃状态,不可见。否则,如果 trx_id 不存在于 trx_ids 集合中,证明事务 trx_id 已经提交了,该行记录可见。

了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过多版本并发控制技术找到它:

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

你能看到 InnoDB 中,MVCC 是通过 Undo Log + Read View 进行数据读取,Undo Log 保存了历史快照,而 Read View 规则帮我们判断当前版本的数据是否可见。

需要说明的是,在隔离级别为读已提交(Read Commit)时,一个事务中的每一次 SELECT 查询都会获取一次 Read View。如表所示:

你能看到,在读已提交的隔离级别下,同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况。

当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View,如下表所示:

 

InnoDB 是如何解决幻读的

不过这里需要说明的是,在可重复读的情况下,InnoDB 可以通过 Next-Key 锁 +MVCC 来解决幻读问题。

在读已提交的情况下,即使采用了 MVCC 方式也会出现幻读。如果我们同时开启事务 A 和事务 B,先在事务 A 中进行某个条件范围的查询,读取的时候采用排它锁,在事务 B 中增加一条符合该条件范围的数据,并进行提交,然后我们在事务 A 中再次查询该条件范围的数据,就会发现结果集中多出一个符合条件的数据,这样就出现了幻读。

出现幻读的原因是在读已提交的情况下,InnoDB 只采用记录锁(Record Locking)。这里要介绍下 InnoDB 三种行锁的方式:

  1. 记录锁:针对单个行记录添加锁。
  2. 间隙锁(Gap Locking):可以帮我们锁住一个范围(索引之间的空隙),但不包括记录本身。采用间隙锁的方式可以防止幻读情况的产生。
  3. Next-Key 锁:帮我们锁住一个范围,同时锁定记录本身,相当于间隙锁 + 记录锁,可以解决幻读的问题。

在隔离级别为可重复读时,InnoDB 会采用 Next-Key 锁的机制,帮我们解决幻读问题。

还是这个例子,我们能看到当我们想要插入球员艾利克斯·伦(身高 2.16 米)的时候,事务 B 会超时,无法插入该数据。这是因为采用了 Next-Key 锁,会将 height>2.08 的范围都进行锁定,就无法插入符合这个范围的数据了。然后事务 A 重新进行条件范围的查询,就不会出现幻读的情况。

好了,今天的课程学到这里,有问题的留个言,别忘了一键三连,下次我们还会再见!

我是黄啊码,码字的码,退。。。退。。。退。。。朝! 

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

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

相关文章

共谋韬略、共巢未来,电巢与韬略“战略合作签约仪式”圆满举办!

前言 2022年10月27日下午&#xff0c;电巢科技与韬略科技齐聚深圳南山&#xff0c;共同举办了隆重的战略合作签约仪式&#xff0c;双方就整合核心资源、共同打造高质量数字化内容等战略方针达成了一致&#xff0c;携手开启合作新篇章。 电巢科技和韬略科技基于相契合的发展战略…

Numpy基础教程

1 Numpy 对象2 Numpy创建numpy.array 构造器来创建numpy.emptynumpy.zerosnumpy.onesnumpy.arangeNumpy索引3 Numpy常用操作numpy.reshapenumpy.reshape(arr,newshape,order C)numpy.transposenumpy.expand_dimsnumpy.squeezeNumpy功能十分强大的python扩展库&#xff0c;数学…

基于Java+Springboot+Vue+elememt美食论坛平台设计实现

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取联系&#x1f345;精彩专栏推荐订阅&#x1f447;&#x1f…

07-树(Tree)结构分析

文章目录树(Tree)结构分析什么是树&#xff1f;树中的相关名词如何理解&#xff1f;什么是二叉树&#xff1f;什么是二叉搜索树&#xff1f;什么是AVL树&#xff1f;什么是2-3树&#xff1f;什么是红黑树&#xff1f;总结&#xff08;Summary&#xff09;树(Tree)结构分析 什么…

【飞桨PaddleSpeech语音技术课程】— 语音识别-流式服务

FastAPI websocket 流式语音识别服务 0. 背景 流式语音识别(Streaming ASR)或者在线语音识别(Online ASR) 是随着输入语音的数据不断增加&#xff0c;实时给出语音识别的文本结果。与之相对的是非实时或者离线语音识别&#xff0c;是传入完整的音频数据&#xff0c;一次给出整…

ppocrlabel简单教学

前言 给我们小白成员的快速上手ppocrlabel的指南 1. ppocr环境配置 建议是先创建一个虚拟环境 【直接参考】&#xff1a;https://blog.csdn.net/weixin_42708301/article/details/119864744 2. ppocrlabel环境安装 【参考文档】PaddleOCR-release-2.6\PPOCRLabel\README_c…

将STM32 内部Flash虚拟成优盘,进行IAP升级

书接上回&#xff0c;上篇文章已经成功的将 STM32 内部FLASH虚拟成优盘进行文件存储了。 【将 STM32 内部Flash虚拟成优盘】https://blog.csdn.net/qq_44810226/article/details/127508789 然后我们开始固件升级流程&#xff1a; 从上图可以看出&#xff0c;固件存储的位置是不…

Python-代码封装思想

继上文所将讲的pythonrequestsUnittest框架&#xff0c;本文将继续分享python代码的进一步封装思想&#xff0c;来提高代码的整体运行效率及整洁性&#xff0c;本文以接口测试框架为例进行讲解。 关于Unittest单元测试框架的具体使用方法链接链接&#xff08;点击跳转&#xf…

MMEngine理解

MMEngine理解1 简介1.1 架构1.2 模块介绍1.2.1 核心模块与相关组件1.2.1 公共基础模块2 上手示例2.1 构建模型2.2 构建数据集和数据加载器2.3 构建评测指标2.4 构建执行器并执行任务3. 基础模块3.1 注册器&#xff08;Registry&#xff09;3.1.1 什么是注册器3.1.2 使用流程3.1…

OSPF路由协议(二)

作者介绍&#xff1a; ⭐️作者&#xff1a;小刘在C站 ❄️每天分享课堂笔记&#xff0c;一起努力&#xff0c;共赴美好人生&#xff01; ☁️夕阳下&#xff0c;是最美的绽放。 目录 一.Router id 二.DR,BDR 三.DR BDR 选举过程 四.ospf 度量值 cost 代价 五.ospf 的数据…

部署vue项目到阿里云服务器

一般前端工程师只会使用npm run serve 在开发环境下验证&#xff0c;那么如何把npm run build 打出的包部署到服务器上进行上线呢&#xff1f;这篇文章就详细阐述这一流程。 1. 购买或试用阿里云服务器 作为新用户可以试用一个月阿里云服务器&#xff0c;阿里云官方网址如下&a…

【数据结构与算法分析】0基础带你学数据结构与算法分析07--二叉树

在学习上一章后&#xff0c;我们对树加以限制&#xff0c;如果树的度为 2&#xff0c;那么就称这颗树为 二叉树 (binary tree)。 二叉树的性质 在一棵二叉树上&#xff0c;有一些重要的性质&#xff1a; 第 i 层 (i∈N) 上最多有 2^(i−1) 个结点层次为 k(k∈N) 的树最多有 …

进程地址空间

目录 程序地址空间 感知虚拟地址空间的存在 进程地址空间 分页 & 虚拟地址空间 Linux2.6内核进程调度队列 程序地址空间 我们在学习C语言的时候了解过程序地址空间的分布&#xff1a; 需要注意的是&#xff1a;程序地址空间不是内存。我们在linux操作系统中通过代码来…

刷题笔记之二(字符串中找出连续最长的数字串+数组中出现次数超过一半的数字+另类加法+计算糖果+进制转换)

目录 1. 多层继承问题 2. 继承中子类的构造要引用super 3. 比较地址 4. 字符串中找出连续最长的数字串(编程题) 5. 数组中出现次数超过一半的数字&#xff08;编程题&#xff09; 6. 另类加法&#xff08;编程题&#xff09; 7. Interface 接口中定义方法 8. 实现或继…

C语言学习(二)之字符串和格式化输入/输出

文章目录一、字符串二、 输入2.1 scanf()作用2.2 两种用法三、输出3.1 printf()3.1.1 printf 四种用法3.1.2 常用输出控制符3.1.3 为什么需要输出控制符一、字符串 字符串是一个或多个字符的序列。如&#xff1a;“Hello World” 双引号不是字符串的一部分。仅告知编译器它括…

【学习笔记】《深入浅出Pandas》第16章:可视化

文章目录16.1 plot方法16.1.1 plot概述16.1.2 plot基础方法16.1.3 图形类型16.1.4 x轴和y轴16.1.5 图形标题16.1.6 字体大小16.1.7 线条样式16.1.8 背景辅助线16.1.9 图例16.1.10 图形大小16.1.11 色系16.1.12 绘图引擎16.1.14 图形叠加16.1.15 颜色的表示16.1.16 解决图形中的…

量子笔记:量子计算 toy python implementation from scratch

目录 0. 概要 1. 量子比特表示&#xff1a;用二维张量表示 2. 张量积的实现 2.1 用scipy.linalg.kron()实现张量积 2.2 用张量积计算双量子系统的基 3. 多量子系统基向量表示和生成 3.1 Helper function: bin_ext 3.2 多量子系统的基的生成 3.3 numpy.matrix numpy.m…

基于多尺度注意力网络单图像超分(MAN)

引言 Transformer的自注意力机制可以进行远距离建模&#xff0c;在视觉的各个领域表现出强大的能力。然而在VAN中使用大核分解同样可以得到很好的效果。这也反映了卷积核的发展趋势&#xff0c;从一开始的大卷积核到vgg中采用堆叠的小卷积核代替大卷积核。 上图展现了MAN网络在…

使用T0,方式2,在P1.0输出周期为400µs,占空比为4:1的矩形脉冲,要求在P1.0引脚接有虚拟示波器,观察P1.0引脚输出的矩形脉冲波形

大家学过一段时间的单片机了&#xff0c;今天我们来说说单片机里的定时器&#xff0c;又叫计数器。首先&#xff0c;我们通过案例来了解一下什么是定时器。 【例】使用T0&#xff0c;方式2&#xff0c;在P1.0输出周期为400s&#xff0c;占空比为4&#xff1a;1的矩形脉冲&…

如何编写优秀的测试用例,建议收藏和转发

1、测试点与测试用例 测试点不等于测试用例&#xff0c;这是我们首先需要认识到的。 问题1&#xff1a;这些测试点在内容上有重复&#xff0c;存在冗余。 问题2&#xff1a;一些测试点的测试输入不明确&#xff0c;不知道测试时要测试哪些。 问题3&#xff1a;总是在搭相似…