博学谷学习记录】超强总结,用心分享 | 架构师 MySQL调优MVCC学习总结

news2025/1/11 21:06:32

文章目录

      • 1 概述
      • 2 快照读与当前读
        • 当前读
        • 快照读
        • 隔离级别:
        • undo log版本链:
      • 3 Read View
        • 3.1 什么是Read View?
        • 3.2 实现原理
        • 3.3 Read View规则(可见性算法)
      • 4 MVCC整体流程
        • 4.1 可重复读是如何工作的?
        • 4.2 读提交是如何工作的?
        • 总结:

MVVC 多版本并发控制

1 概述

MVCC(Multiversion Concurrency Control),多版本并发控制。它和undo log中的版本链息息相关,MVVC通过数据行的多个版本来实现数据库的并发控制

简单的说就是当前事务查询另一个事务正在更改的行(如果此时读取就会发生脏读),不用加锁等待,而是读取该数据的历史版本,降低响应时间。

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

2 快照读与当前读

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理 读-写冲突 ,做到 即使有读写冲突时,也能做到 不加锁 , 非阻塞并发读 ,而这个读指的就是 快照读 , 而非 当前读 。当前读 实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。

  • 当前读

当前读:读的是最新数据 (增删改、加锁的查询)

当前读读取的记录一定是最新的数据,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

加锁的读被称为当前读,还有数据的增删改都是要先读取数据的,这一读取过程也是当前读。

SELECT * FROM t LOCK IN SHARE MODE; # 共享锁
SELECT * FROM t FOR UPDATE; # 排他锁
UPDATE SET t..

也就是MVCC进行读—写,读是读不加锁的 快照读,写是必须先读到最新的再去写,是当前读。

  • 快照读

快照读:读的是历史版本(普通的查询)

快照读又叫一致性读,读取的是数据行的快照版本。在MySQL中,普通的select语句(不加for update或lock in share mode的select语句)默认就是使用的快照读,不加锁。

SELECT * FROM table WHERE ...

之所以这样,是因为快照读可以避免加锁操作,降低开销。

注意:当事务的隔离级别是串行时,快照读就没有用了,会退化为当前读。

隔离级别:

在MySQL中默认的隔离级别就是可重复读RR,可以解决不可重复读问题,在MySQL中,特别的还额外支持解决幻读问题

它是如何解决幻读问题的呢?有两种方式:

可重复读级别如何解决幻读的?(面试题答案)

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

undo log版本链:

隐藏字段

对应InnoDB来说,聚簇索引中的每个记录都包含了两个必要的隐藏字段:

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

[)(MySQL笔记.assets/image-20230527173608948.png)]

回滚指针:指向之前的版本地址

举例:

有一个id为8的事务创建了一条数据,那么该记录的示意图大概如下:

假设之后两个id分别为10、20的事务对这条记录进行update操作,流程如下:

在这里插入图片描述

每次修改都会生成一个undo log日志,每个日志都相互链接,构成版本链,此时该条数据的示意图如下:

在这里插入图片描述

对该记录每次更新后,都会将旧值放到一条 undo日志 中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为版本链,版 本链的头节点就是当前记录最新的值。

每个版本中还包含生成该版本时对应的事务id 。

3 Read View

有了undo log就可以读取到记录的历史版本,那么在什么情况下,读取哪个版本的记录呢?这就用到了Read View,它帮我们解决了行的可见性问题。

3.1 什么是Read View?

Read View就是当某个事务在使用MVVC机制进行快照读操作时产生的读视图。

3.2 实现原理

四种隔离级别里,读未提交和串行化是不会使用MVVC的,因为读未提交直接读取某个数据的最新数据即可,串行化是通过加锁来读的。

读已提交可重复读都必须保证读到的数据都是其他事务提交了的,所以,其他事务修改了数据但是还未提交,我们不能够访问该数据,但可以通过MVVC机制读取该记录的历史版本,核心问题就是需要判断版本链中的哪条历史版本是当前事务可见的,这也是ReadView要解决的问题。

Read View包含4个比较重要的内容:

在这里插入图片描述

  • creator_trx_id创建这个Read View的事务id,Read View和事务是一一对应的。(只有事务对表中的记录做修改时才会为事务分配事务id,否则一个事务中只有读操作,该事务的id默认为0。)
  • m_ids指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务
  • min_trx_id指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
  • max_trx_id这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
注意:max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比如,现在有id为1, 2,5这三个事务,之后id为5的事务提交了。那么一个新的读事务在生成ReadView时, m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是6。

3.3 Read View规则(可见性算法)

在这里插入图片描述

当某个事务有了Read View,访问某条记录时,需要按照下面的步骤判断该记录的哪个版本可见:

创建 Read View 后,我们可以将记录中的 trx_id 划分这三种情况:

在这里插入图片描述

一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:

  • 如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,表示这个版本的记录是在创建 Read View 已经提交的事务生成的,所以该版本的记录对当前事务可见
  • 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View 才启动的事务生成的,所以该版本的记录对当前事务不可见
  • 如果记录的 trx_id 值在 Read View 的min_trx_id和max_trx_id之间,需要判断 trx_id 是否在 m_ids 列表中:
    • 如果记录的 trx_id m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见
    • 如果记录的 trx_id m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见

这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。

4 MVCC整体流程

了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:

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

4.1 可重复读是如何工作的?

可重复读隔离级别是启动事务时进行第一次查询前生成一个 Read View,然后整个事务期间都在用这个 Read View

假设事务 A (事务 id 为51)启动后,紧接着事务 B (事务 id 为52)也启动了,那这两个事务创建的 Read View 如下:

在这里插入图片描述

事务 A 和 事务 B 的 Read View 具体内容如下:

  • 在事务 A 的 Read View 中,它的事务 id 是 51,由于它是第一个启动的事务,所以此时活跃事务的事务 id 列表就只有 51,活跃事务的事务 id 列表中最小的事务 id 是事务 A 本身,下一个事务 id 则是 52。

  • 在事务 B 的 Read View 中,它的事务 id 是 52,由于事务 A 是活跃的,所以此时活跃事务的事务 id 列表是 51 和 52,活跃的事务 id 中最小的事务 id 是事务 A,下一个事务 id 应该是 53。

接着,在可重复读隔离级别下,事务 A 和事务 B 按顺序执行了以下操作:

  • 事务 B 读取小林的账户余额记录,读到余额是 100 万;

  • 事务 A 将小林的账户余额记录修改成 200 万,并没有提交事务;

  • 事务 B 读取小林的账户余额记录,读到余额还是 100 万;

  • 事务 A 提交事务;

  • 事务 B 读取小林的账户余额记录,读到余额依然还是 100 万;

  • 接下来,具体分析

    事务 B 第一次读小林的账户余额记录,在找到记录后,它会先看这条记录的 trx_id,此时发现 trx_id 为 50,比事务 B 的 Read View 中的 min_trx_id 值(51)还小,这意味着修改这条记录的事务早就在事务 B 启动前提交过了,所以该版本的记录对事务 B 可见的,也就是事务 B 可以获取到这条记录。

    接着,事务 A 通过 update 语句将这条记录修改了(还未提交事务),将小林的余额改成 200 万,这时 MySQL 会记录相应的 undo log,并以链表的方式串联起来,形成版本链,如下图:

在这里插入图片描述

你可以在上图的「记录的字段」看到,由于事务 A 修改了该记录,以前的记录就变成旧版本记录了,于是最新记录和旧版本记录通过链表的方式串起来,而且最新记录的 trx_id 是事务 A 的事务 id(trx_id = 51)。

然后事务 B 第二次去读取该记录,发现这条记录的 trx_id 值为 51,在事务 B 的 Read View 的 min_trx_id 和 max_trx_id 之间,则需要判断 trx_id 值是否在 m_ids 范围内,判断的结果是在的,那么说明这条记录是被还未提交的事务修改的,这时事务 B 并不会读取这个版本的记录。而是沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 「小于」事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是小林余额是 100 万的这条记录。

最后,当事物 A 提交事务后,由于隔离级别时「可重复读」,所以事务 B 再次读取记录时,还是基于启动事务时创建的 Read View 来判断当前版本的记录是否可见。所以,即使事物 A 将小林余额修改为 200 万并提交了事务, 事务 B 第三次读取记录时,读到的记录都是小林余额是 100 万的这条记录

就是通过这样的方式实现了,「可重复读」隔离级别下在事务期间读到的记录都是事务启动前的记录。

4.2 读提交是如何工作的?

读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View
在这里插入图片描述

也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。

那读提交隔离级别是怎么工作呢?我们还是以前面的例子来聊聊。

假设事务 A (事务 id 为51)启动后,紧接着事务 B (事务 id 为52)也启动了,接着按顺序执行了以下操作:

  • 事务 B 读取数据(创建 Read View),小林的账户余额为 100 万;
  • 事务 A 修改数据(还没提交事务),将小林的账户余额从 100 万修改成了 200 万;
  • 事务 B 读取数据(创建 Read View),小林的账户余额为 100 万;
  • 事务 A 提交事务;
  • 事务 B 读取数据(创建 Read View),小林的账户余额为 200 万;

那具体怎么做到的呢?我们重点看事务 B 每次读取数据时创建的 Read View。前两次 事务 B 读取数据时创建的 Read View 如下图:

在这里插入图片描述

我们来分析下为什么事务 B 第二次读数据时,读不到事务 A (还未提交事务)修改的数据?

事务 B 在找到小林这条记录时,会看这条记录的 trx_id 是 51,在事务 B 的 Read View 的 min_trx_id 和 max_trx_id 之间,接下来需要判断 trx_id 值是否在 m_ids 范围内,判断的结果是在的,那么说明这条记录是被还未提交的事务修改的,这时事务 B 并不会读取这个版本的记录。而是,沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 「小于」事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是小林余额是 100 万的这条记录。

我们来分析下为什么事务 A 提交后,事务 B 就可以读到事务 A 修改的数据?

在事务 A 提交后,由于隔离级别是「读提交」,所以事务 B 在每次读数据的时候,会重新创建 Read View,此时事务 B 第三次读取数据时创建的 Read View 如下:

事务 B 在找到小林这条记录时,会发现这条记录的 trx_id 是 51,比事务 B 的 Read View 中的 min_trx_id 值(52)还小,这意味着修改这条记录的事务早就在创建 Read View 前提交过了,所以该版本的记录对事务 B 是可见的

正是因为在读提交隔离级别下,事务每次读数据时都重新创建 Read View,那么在事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。

总结:

幻读解决方案:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

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

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

相关文章

ChatGPT国内镜像,以及如何使用ChatGPT帮你制作PPT

一:前言 ChatGPT:智能AI助你畅聊天地 在现代人日益忙碌的生活中,难免需要一些轻松愉快的聊天来放松身心。而现在,有了 ChatGPT,轻松愉快的聊天变得更加智能、有趣且不受时间、地点限制! 什么是 ChatGPT&…

Python中模块的使用方法4

1 模块、包和库的区别 Python中,模块的英文是“module”,是一个以py为后缀名的文件;包的英文是“package”,是一个包含了多个模块的目录;库的英文是“library”,包含了具有相关功能的包和模块。 2 模块的…

ChatGPT 在自动化测试领域的应用,我们真的要被代替了吗?

目录 前言 一、ChatGPT 简介 二、ChatGPT 的应用场景 三、ChatGPT 的优势 四、ChatGPT 的局限性 五、ChatGPT 在自动化测试领域的拓展应用 六、ChatGPT真的可以代替软件测试人员吗 七、结语 前言 人工智能技术在近年来得到了快速发展,不少领域都开始尝试融合…

入理解深度学习——正则化(Regularization):多任务学习

分类目录:《深入理解深度学习》总目录 多任务学习是通过合并几个任务中的样例(可以视为对参数施加的软约束)来提高泛化的一种方式。正如额外的训练样本能够将模型参数推向具有更好泛化能力的值一样,当模型的一部分被多个额外的任务…

redis主从复制策略的原理:主从节点间如何同步数据?

redis的主从复制原理经历了多个版本的更新。 redis2.8之前的SYNC方案 命令: SYNChttps://www.yuque.com/snailclimb/mf2z3k/ks9olb19hc9wse5k#5935f46a 存在的问题: slave加载RDB的过程中不能对外提供读服务slave和master断开连接后,sla…

HCIA-VLAN间通信之路由器

目录 路由信息获取方式: 最佳路由条目选择(根据路由的优先级和开销选择): VLAN间通信 方案1,使用路由器的物理接口来实现不同vlan间的通信 方案2:使用路由器的子接口来实现 路由:指导报文转…

【Vue】二:Vue核心处理---计算属性 监视属性

文章目录 1.计算属性示例2. 监听属性3.补充 1.计算属性示例 实际上计算属性与methods中定义方法基本上没有什么区别,只是计算属性基于响应式依赖缓存,只要数据没有发生改变,计算属性从缓存中取值,只有当数据发送改变,才…

Linux 用户与组群管理

1 用户账户与群组概念 Linux操作系统是多用户多任务的操作系统,允许多个用户同时登录到系统,使 用系统资源。用户账户是用户的身份标识。用户通过用户账户可以登录到系统, 并且访问已经被授权的资源。系统依据账户来区分属于每个用户的文件…

Flutter 笔记 | Flutter 动画

Flutter中的动画抽象 为了方便开发者创建动画,不同的UI系统对动画都进行了一些抽象, Flutter中也对动画进行了抽象,主要涉及 Animation、Curve、Controller、Tween这四个角色,它们一起配合来完成一个完整动画,下面我们…

整数在内存中原来是这样存储的,看完表示头好痒,感觉要长脑子了!

本篇文章来介绍一下整形在内存中的存储,内容丰富,干货慢慢。 目录 1.整形家族 2.整形在内存中的存储 3.大端小端存储 4.练习 1.整形家族 在开始之前,我们先来简单回顾一下整形家族: char unsigned char signed char short u…

chatgpt赋能python:Python补0——让你的数字更规整

Python 补0——让你的数字更规整 在编写代码的过程中,我们经常需要将数字格式化,以便更好地呈现给用户。例如,一个价格可能需要显示为“$100.00”而不是“$100”或“$100.0”。这时候,我们就需要使用 Python 的补0功能。本文将为…

【015】C++的函数详解

C的函数详解 引言一、函数介绍1.1、函数的概述1.2、函数的分类 二、函数定义、声明、调用等2.1、定义函数2.2、函数声明2.3、调用函数2.4、默认参数2.5、函数重载2.6、函数的调用过程 三、函数的传参3.1、普通变量作为函数的参数3.2、数组作为函数的参数 总结 引言 &#x1f4a…

浅浅入门SpringCloud

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有…

如何在Linux机器中测试存储/磁盘I/O性能?

在Linux环境中,了解存储/磁盘I/O性能对于评估系统性能和优化存储子系统非常重要。通过测试存储/磁盘I/O性能,我们可以确定磁盘的读写速度、延迟和吞吐量等指标。本文将介绍几种常用的方法来测试Linux机器中的存储/磁盘I/O性能。 方法一:使用d…

测试开发工程师必问面试题

1.对测试开发的理解 测试开发首先离不开测试,而软件测试是指,在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。 而且,现在不仅仅是通过手工测试来发…

【C++】容器篇(四)—— queue的基本介绍以及模拟实现

前言: 在上期博文中我带大家对stack进行深入的学习,本期我将带领学习的是关于 queue的基本知识,并且还将给大家介绍并实现 priority_queue。接下来,让我们正式本期的内容。 目录 (一)queue的基本介绍 &…

83.响应式设计原则

什么是响应式设计? ● 使网页根据任何可能的屏幕尺寸(窗口或视口尺寸)调整其布局和视觉风格的设计技术。 ● 在实践中,这意味着响应式设计使网站可以在所有设备上使用,如台式电脑、平板电脑和手机。 ● 这是一套做法&…

window安装docker Desktop和wsl2

目录 一、先到微软商店下载terminal (也就是power shell,后续命令都在这个里面执行) 二、安装docker Destop 1.打开控制面板 2.勾选Hyper-V服务 3、根据提示重启电脑,等待更新即可 二.启动Docker Desktop 2.1 报错,提示需要最新的WSL 方式一&#…

阿里云服务器CPU内存怎么选?几核几G合适?

阿里云服务器配置怎么选择?CPU内存、公网带宽和系统盘怎么选择?个人用户选择轻量应用服务器或ECS通用算力型u1云服务器,企业用户选择ECS计算型c7、通用型g7云服务器,阿里云服务器网分享阿里云服务器配置选择方法: 目录…

Day4 计算糖果、进制转换

✨个人主页: 北 海 🎉所属专栏: C/C相关题解 🎃操作环境: Visual Studio 2019 版本 16.11.17 文章目录 选择题1、C函数 编程题1、计算糖果2、进制转换 选择题 1、C函数 题目:下列程序执行后,输出的结果为…