一文搞懂后端面试之MySQL MVCC【中间件 | 数据库 | MySQL | 隔离级别 | Read View】

news2024/11/25 17:20:51

为什么需要MVCC

锁本身就是用于并发控制的,那么为什么InnoDB还要引入MVCC,读写都加锁不就可以控制住并发吗?
锁确实可以,但是性能太差。如果是纯粹的锁,那么写和写、读和写、读和读之间都是互斥的。如果是读写锁,那么写和写、读和写之间依旧是互斥的。
数据库和一般的应用有一个很大的区别,就是数据库即使是读,也不能被写阻塞住
所以数据库要有一种机制,避免读写阻塞。在理解了为什么MVCC必不可少后,现在你需要进一步了解一个和MVCC紧密关联的概念:隔离级别

隔离级别

数据库的隔离级别是一组规则,用来控制并发访问数据库时如何分配、保护和共享资源。不同的隔离级别在不同的并发控制策略之间进行调整,从而提供了不同的读写隔离级别和安全性。隔离级别代表了一个事务是否了解别的事务以及了解程度怎么样。
MySQL的隔离级别有四个。

  1. 读未提交 Read Uncommitted 是指一个事务可以另外一个事务尚未提交的修改
    在这里插入图片描述
  2. 读已提交 Read Committed 是指一个事务只能看到已经提交的事务的修改。如果在事务执行过程中有别的事务提交了,那么事务还是能够看到别的事务最新提交的修改
    在这里插入图片描述
  3. 可重复读 Repeatable Read 是指在这一个事务内部读同一个数据多次,读到的结果都是同一个。这意味着,即使在事务执行过程中有别的事务提交,这个事务依旧看不到别的事务提交的修改。这是MySQL默认的隔离级别。
    在这里插入图片描述
  4. 串行化 Serializable 是指事务对数据的读写都是串行化的

从上到下,隔离性变强了但是性能变差了,所以一个提升MySQL性能最简单的方式,就是把隔离级别往下调,这也是我们的一个亮点方案。

和隔离级别密切相关的概念是脏读、幻读和不可重复读 这三个读异常。

  • 脏读:读到了别的事务还没有提交的数据。之所以叫做脏读,就是因为未提交数据可能会被回滚掉。
  • 不可重复读:在一个事务执行过程中,对同一行数据读到的结果不同
  • 幻读:事务执行过程中,别的事务插入了新的数据并且提交了,然后事务在后续步骤里读到了这个新的数据。
    在这里插入图片描述
    用一个表用来描述隔离级别和三种读异常的关系:
    在这里插入图片描述
    理论上,可重复读是没有解决幻读的,但是因为MySQL因为使用了临键锁,因此它的可重复读隔离级别已经解决了幻读问题。

此外还有一个相似的概念:快照读和当前读。
快照读就是在事务开始的时候创建了一个数据的快照,在整个事务过程中都读这个快照;
当前读,则是每次都去读最新数据。

MySQL在可重复读这个隔离级别下,查询的执行效果和快照读非常接近。

版本链

为了实现MVCC,InnoDB引擎给每一行都加了两个额外的字段trx_idroll_ptr

  • trx_id:事务ID,也叫做事务版本号。MVCC里面的V指的是这个数字,每一个事务在开始的时候就会获得一个ID,然后这个事务内操作的行的事务ID,都会被修改为这个事务的ID
  • roll_ptr:回滚指针,InnoDB通过roll_ptr把每一行的历史版本串联在一起。

实际上,InnoDB引擎还隐式地插入了另外一个列row_id,如果你没有设置任何主键,那么这个列就会被当作主键来使用。但是它其实和MVCC没太大关系,所以不需要关注。

下面用一个例子来说明MVCC是如何利用这两个列地。

假设最开始我插入了一行数据,我插入数据的这个事务的ID是100,那么这个时候数据行看起来是这样的。
在这里插入图片描述
假设有一个事务A拿到了ID 101,然后把x的值修改为15,那么就会变成这样。
在这里插入图片描述
这个时候,事务A修改后的roll_ptr会指向初始状态的数据。假如现在再来一个事务B拿到ID 102,要把数据修改为20,那么就会变成下面这样。

在这里插入图片描述
这条链就是大名鼎鼎的版本链,这个版本链存储在所谓的undolog里。

问题来了:假如这个时候我有一个新的事务 C,我要读 x 的值,那么我该读取 trx_id 为几的数据呢?这就涉及到了另外一个和 MVCC 紧密相关的概念:Read View

Read View

可以理解为一种可见性规则,undolog里存放着历史版本的数据,当事务内部要读取数据的时候,Read View 就被用来控制这个事务应该读取哪个版本的数据

Read View 最关键的字段叫做m_ids代表的是当前已经开始,但是还没有结束的事务ID,也叫做活跃事务ID。
Read View 只用于已提交读和可重复读两个隔离级别,它用于这两个隔离级别的不同点就在于什么时候生成Read view

  • 已提交读:事务每次发起查询的时候,都会重新创建一个新的Read view
  • 可重复读:事务开始的时候,创建Read view

一个很有意思的类比:已提交读就像你的渣男朋友,你每次见到他,他都会换一个新对象;而可重复读就是一个痴情男,你每次见到他,看到的都是他高中时候谈的对象。

Read view与已提交读

在已提交读的隔离级别下,每一次查询都会产生一个新的Read view。这意味着在事务执行过程中,Read view是在不断变动的。假如说现在已经有三个事务了,状态分别是已提交、未提交、未提交。
在这里插入图片描述
假如说现在新开了一个事务A,分配给它的ID是4。如果这个时候A开始查询x的值,那么MySQL会创建一个新的Read view,其中m_ids = 2,3 。事务A发现最后一个已经提交的事务trx_id=1,对应的x是1,于是事务A读到x=1。
在这里插入图片描述
这个时候事务2提交了,事务A再次读取x,这个时候MySQL又会生成一个新的Read viewm_ids=3,因此事务A会读取到x=4
在这里插入图片描述

Read view与可重复读

在可重复读的隔离级别下,数据库会在事务开始的时候生成一个Read view,这意味着整个Read view在事务执行过程中都是稳定不变的。
用前面的例子来说明,就是在事务A开始的时候就会创建出来一个Read view m_ids=2,3
在这里插入图片描述
这个时候事务A去读x的数据,毫无疑问,读出来都是1
在这里插入图片描述
这个时候如果事务2提交了,然后事务A想要再去读x的值,Read view不会发生变化,即m_ids=2,3。所以,虽然事务2提交了,但是事务A不知道这回事,因此还是读到x=1
在这里插入图片描述
万一这时候有一个新事务 ID = 5 开始了,并且也提交了。那么事务 A 并不会读取这个新事务的数据,因为新事务 ID 已经大于事务 A 的 ID 了(5 > 4),事务 A 知道这是一个比它还要晚的事务,所以会忽略新的事务的修改。

Read View 总结

在这里插入图片描述
在这里插入图片描述
实际上和Read View相关的概念还有三个

  • m_up_limit_id 指的是m_ids中的最小值
  • m_low_limit_id 指的是下一个分配的事务ID
  • m_creator_trx_id 当前事务ID

在这里插入图片描述
m_up_limit_id 在左边,而 m_low_limit_id 在右边

面试准备

了解清楚公司数据库的隔离级别,如果公司设置的不是默认的隔离级别,那么要搞清楚为什么不使用默认的隔离级别。尤其是用了未提交读、串行化两个隔离级别,更加要弄清楚。

在面试过程中,面试官会出一些很难让人反应过来的问题,比如说面试官会口头构造一条版本链。

我现在有三个事务,ID 分别是 101、102、103。如果事务 101 已经提交了,但是 102、103 还没提交。这个时候,我开启了一个事务,准备读取数据,那么我读到的是哪个事务的数据?
如果这时候事务 103 提交了,但是 102 还没提交,那么会读到谁的呢?

第一个问题是事务101
第二个问题需要根据隔离级别来回答了

基本思路

有的时候在面了锁之后,将话题引到MVCC,问你为什么有了锁还需要MVCC?回答的关键词是避免读写阻塞

单纯使用锁的时候,并发性能会比较差,即使是在读写锁这种机制下,读和写依旧是互斥的。而数据库是一个性能非常关键的中间件,如果某个线程修改某条数据就让其他线程都不能读到这条数据,这种性能损耗是无法接受的。所以InnoDB引擎引入了MVCC就是为了减少读写阻塞。

大部分的时候,面试官在问MVCC的时候,都直接问你这几个问题

  • 你是否了解MVCC?
  • MVCC是什么
  • MySQL的InnoDB引擎是怎么控制数据并发访问的?
  • 当一个线程在修改数据的时候,另外一个线程还能不能读到数据

按照:基本定义、实现机制、隔离级别的逻辑顺序来回答

MVCC是MySQL InnoDB引擎用于控制数据并发访问的协议。MVCC主要是借助版本链来实现的。在InnoDB引擎里面,每一行都有两个额外的列,一个是trx_id,代表的是修改这一行数据的事务ID;另外一个是roll_ptr,代表的是回滚指针。InnoDB通过回滚指针,将数据的不同版本串联起来,也就是版本链。这些串联起来的历史版本,被放到了undolog里面。当某一个事务发起查询的时候,MVCC会根据事务的隔离级别来生成不同的Read View,从而控制事务查询最终得到的结果。

首先,回答里提到了undolog,面试官可能追问undolog、redolog或binlog的细节,这一部分可以把话题引到下一节课的内容。
其次,回答中提到了隔离级别,并提到了Read View是和隔离级别有关的东西,面试官就会非常深入的问隔离级别的基本定义、MVCC是怎么利用Read View来实现已提交读和可重复读的。

在回答的时候,要先解释清楚四个隔离级别和三个读异常,然后强调一下InnoDB引擎。

在MySQL的InnoDB引擎里,使用了临键锁来解决幻读的问题,所以实际上MySQL InnoDB引擎的可重复读隔离级别也没有幻读的问题。一般来说,隔离级别越高,性能越差,所以我之前在公司做的一个很重要的事情,就是推动隔离级别降低为已提交读。

这个回答的最后,就可以尝试把话题引导到下面的亮点方案中。

亮点方案

重点要描述清楚两方面的内容

  1. 推动公司把隔离级别从默认的可重复读降低为已提交读
  2. 在已提交读的基础上,万一需要利用可重复读的特性,该怎么办?

从前面的内容中你已经知道,MySQL 的默认隔离级别是可重复读,实际上互联网的很多应用都调整过这个隔离级别,降低为已提交读。那么你在面试的时候可以考虑使用这个来作为你的亮点方案。首先你要强调为什么要改

最开始我来到公司的时候,我们的数据库隔离级别都是使用默认的隔离级别,也就是可重复读。但其实我们的业务场景很少利用可重复读的特性,比如说几乎全部事务内部对某一个数据都是只读一次的
并且,可重复读比已提交读更加容易引起死锁的问题,比如说我们之前就出现过一个因为临键锁引发的死锁问题。而且已提交读的性能要比可重复读更好。所以综合之下,我就推动公司去调整隔离级别,将数据库的默认隔离级别降低为已提交读。

在这种情况下,面试官可能会追问你:“在调整了事务级别之后,万一需要可重复读的特性了,你怎么办?”
首先你要理解在什么样的场景下你才会需要可重复读这个隔离级别。

  • 你需要在事务中发起两次同样的查询,并且你希望两次得到的结果是一样的。
  • 你需要避开幻读,也就是事务开始之后,即便有别的事务插入了数据并且提交了,你也不希望读到这个新数据。

但是仔细想想,你真的存在这种场景吗?或者说,你真的没得选,以至于一定要使用可重复读这个隔离级别吗?
答案是几乎没有。大部分出现可重复读的需求都是因为代码没有写好,或者说至少可以通过改造业务来实现。比如说常见的可重复读,既然你需要读多次,那么自然可以在第一次读完之后缓存起来

不过幻读是没有办法通过业务改造来解决的。但是在业务层面上,幻读一般不会被认为是一个问题,原因有两点:一是你分不清是不是幻读。比如说你在事务 A 里面读到了一条数据,你判断不出来它是在事务 A 开始之前就插入的,还是在事务 A 开始之后,事务 B 才插入并且提交的。
在这里插入图片描述
二是事务提交往往意味着业务已经结束,所以读到一个已经提交的事务的数据,不会损害业务的正确性。也就是说,如果事务A在开始之后,事务B才插入数据并且提交,那么这个时候事务A完全可以认为事务B所在的整个业务已经结束了,所以读出来也没什么问题。

回答的关键词是改造业务

正常来说是不推荐使用可重复读的,因为在我们的业务环境下想不到有什么场景非得使用可重复读这个隔离级别。
之前在推动降低隔离级别的时候,其实重构过一些业务。这一类业务就是在一个事务里面发起了两个同样的查询,比如在UPDATE之后又立刻查询,这种查询还必须走主库,不然会有主从延迟的问题。
这种业务可以通过缓存第一次查询的数据来避免第二次查询。但是这种改造一般是避不开幻读的。不过在业务上幻读一般不是问题。一方面是业务层面上区分不出来是否是幻读。另外一方面,事务提交了往往代表业务已经结束,那么发生幻读了,业务依旧是正常的。比如说事务 A 读到了事务 B 新插入的数据,但是事务 B 本身已经提交了,那么事务 A 就认为事务 B 所在的业务已经完结了,那么读到了就读到了,并不会出什么问题。

兜底的手段是:指定隔离级别

万一不能改造业务,那么还有一个方法,就是直接在创建事务的时候指定隔离级别。我前面调整的都是数据库的默认隔离级别,实际上还可以在 Session 或者事务这两个维度上指定隔离级别。

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

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

相关文章

C#TreeView控件应用

1、代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;namespace TestApp…

两数之和 II(LeetCode)

题目 给你一个下标从 1 开始的整数数组 ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 的两个数。 如果设这两个数分别是 和 ,则 。 以长度为 的整数数组 形式返回这两个整数的下标 和 。 你可以假设每个输入…

【从零开始一步步学习VSOA开发】并行RPC服务端

并行RPC服务端 概念 之前 RPC 使用方式中的所有回调函数都是串行执行的,VSOA 为 C 语言开发版本提供了并行处理 RPC 请求的功能,开发者可以通过并发 RPC 相关接口创建线程池并行处理 RPC 请求。并发服务模式通过多线程来实现,能有效提高响应…

8.06 C++作业

使用类定义实现隔离储存计算 1.头文件 #ifndef CLASS_H #define CLASS_H #include <iostream> using namespace std;class rect { private:int width;int height; public:void init(int width,int height);void show();void setw(int w);void seth(int h); };#endif //…

gradle安装及配置

文章目录 一、下载安装包二、解压文件三、环境变量配置四、验证安装结果五、配置国内源六、IDEA配置 一、下载安装包 从gradle官网下载安装包&#xff0c;官网地址为&#xff1a;https://gradle.org/releases/ 我们只需要下载编译好的文件即可。 二、解压文件 解压文件到指定…

睡前故事—星际旅行者

文章目录 欢迎来到《Bedtime Stories Time》。这是一个我们倾听、放松、并逐渐入睡的播客。感谢你收听并支持我们&#xff0c;希望你能将这个播客作为你睡前例行活动的一部分。今晚我们将讲述《星际旅行者》的故事。一个宁静的夜晚&#xff0c;希望你现在已经感到平静、放松&am…

PXE的使用

配置前提 1、挂载镜像源&#xff0c;可正常下载软件 [rootredhat-7 ~]# mkdir -p /rhel7 ----创建挂载点目录 [rootredhat-7 ~]# mount /dev/sr0 /rhel7/ ----挂载镜像源至挂载点&#xff08;临时挂载&#xff0c;重启失效&#xff09;[rootredhat-7 ~]# vim /etc/yum.repos.…

[CR]厚云填补_GridDehazeNet+

GridDehazeNet: An Enhanced Multi-Scale Network With Intra-Task Knowledge Transfer for Single Image Dehazing Abstract 雾霾等恶劣天气条件会降低自动驾驶和智能交通系统的性能。作为一种潜在的补救措施&#xff0c;我们提出了一种增强的多尺度网络&#xff0c;称为GridD…

鸿蒙(API 12 Beta2版)媒体开发【使用AudioRenderer开发音频播放功能】

音频播放开发概述 如何选择音频播放开发方式 系统提供了多样化的API&#xff0c;来帮助开发者完成音频播放的开发&#xff0c;不同的API适用于不同音频数据格式、音频资源来源、音频使用场景&#xff0c;甚至是不同开发语言。因此&#xff0c;选择合适的音频播放API&#xff…

conda pack迁移环境

文章目录 下载conda pack打包已有环境还原环境 因为有的服务器没有网络&#xff0c;如果想要安装自己的虚拟环境&#xff0c;就需要在有网络的服务器安装好环境后迁移到没有网络的服务器。conda-pack是一个命令行工具&#xff0c;用于打包 conda 环境&#xff0c;pip inatall和…

【Python实战】完美实现 WPS 会员功能,自动化处理 PDF 文档(建议收藏)

数字化办公已成常态&#xff0c;文档管理和处理是很多小伙伴的日常工作。 PDF&#xff08;Portable Document Format&#xff09;文档因其跨平台兼容性和格式固定性而备受青睐。 然而&#xff0c;对于非WPS会员用户而言&#xff0c;一些高级功能如批量处理、格式转换、添加水…

【SpringMVC】详细介绍SpringMVC的执行流程

目录 1. 概念 2.SpringMVC工作原理 3. springMVC的简单使用 1.在pom.xml中导入相关依赖 2.在web.xml中配置dispatcherServlet 3.创建springMVC.xml核心配置文件 1. 概念 什么是MVC&#xff1f; MVC是下面三个组件的简写&#xff0c;模型&#xff08;Model&#xff09;、视图…

mathtype7永久激活密钥咋子哪里获取?2024最新破解版下载附安装教程

在数字化时代&#xff0c;我们每天都与文字和符号打交道。无论是撰写论文、准备报告还是编写程序&#xff0c;数学公式的输入都是不可或缺的一环。但你有没有遇到过这样的困扰&#xff1a;在Word文档中编辑复杂的数学公式时&#xff0c;操作繁琐且不直观&#xff1f; 别担心&a…

鸿蒙图形开发【3D引擎接口示例】

介绍 本实例主要介绍3D引擎提供的接口功能。提供了ohos.graphics.scene中接口的功能演示。 3D引擎渲染的画面会被显示在Component3D这一控件中。点击按钮触发不同的功能&#xff0c;用户可以观察渲染画面的改变。 效果预览 使用说明 在主界面&#xff0c;可以点击按钮进入不…

【书生大模型实战营第三期】基础岛 第1关 书生大模型全链路开源体系

欢迎大家参与第三期书生大模型实战营&#xff01;&#xff01;&#xff01; 1. 书生浦语开源历程 从23年7月开始&#xff0c;直到今年7月&#xff0c;书生浦语先后开源了 InternLM、InternLM2 核性能更好的 InternLM2.5。 2. InternLM2.5 的优势 其中&#xff0c;最新的 Intern…

计算机语言-CSP初赛知识点整理

历年真题 [2020-CSP-J-第2题] 编译器的主要功能( ) A. 将源程序翻译成机器指令代码 B. 将源程序重新组合 C. 将低级语言翻译成高级语言 D. 将一种高级语言翻译成另一种高级语言 [2021-CSP-J-第1题] 以下不属于面向对象程序设计语言的是&#xff08;&#xff09;。 A. C B. Pyt…

【读点论文】场景图像中文本检测和识别关键技术研究-博士学位论文

文本是人类获取信息及社会交流的重要手段&#xff0c;从图像准确读取文本对人类的生产生活至关重要。现有方法通常将文本读取细分为文本检测、文本识别、端到端文本识别三个子任务。其中文本检测的目的是定位出图像中文本的位置&#xff0c;文本识别旨在识别出文本区域的字符序…

高仲富:49岁搞AI,白天种菜卖菜,晚上学数学搞程序

这是《开发者说》的第13期&#xff0c;本期我们邀请的开发者是高仲富&#xff0c;曾是一位数学老师&#xff0c;自学成为一名程序员&#xff0c;在北京漂过&#xff0c;后逃回了成都&#xff0c;一边与病魔抗争&#xff0c;一边写代码&#xff0c;一写就是15年&#xff0c;制作…

Electron 集成SQlite FTS5 实现百万级数据的倒排索引

背景 在产品迭代时&#xff0c;个人版产品已经将联系人和消息实时备份到本地&#xff0c;而消息的备份的目的仍然是为了快速查询对自己有用的上下文&#xff0c;并能快速定位到这些用户以及这些有用的信息。另外包括未来喂给 chatgpt-4o 的数据也是需要调用搜索获取的&#xff…

39. 647. 回文子串,516.最长回文子序列, 动态规划总结

确定dp数组以及下标的含义。如果大家做了很多这种子序列相关的题目&#xff0c;在定义dp数组的时候 很自然就会想题目求什么&#xff0c;我们就如何定义dp数组。绝大多数题目确实是这样&#xff0c;不过本题如果我们定义&#xff0c;dp[i] 为 下标i结尾的字符串有 dp[i]个回文串…