MySQL的undo log 与MVCC

文章目录

  • 概要
  • 一、undo日志
    • 1.undo日志的作用
    • 2.undo日志的格式
    • 3. 事务id(trx_id)
  • 二、MVCC
    • 1.版本链
    • 2.ReadView
    • 3.REPEATABLE READ —— 在第一次读取数据时生成一个ReadView
    • 4.快照读与当前读
  • 小结


概要

Undo Log:数据库事务开始之前,会将要修改的记录存放到 Undo 日志里,当事务回滚时或者数据库崩溃时,可以利用 Undo 日志,撤销未提交事务对数据库产生的影响。

MVCC(Multi Version Concurrency Control)被称为多版本控制,是指在数据库中为了实现高并发的数据访问,对数据进行多版本处理,并通过版本链保证事务能看到自己应该看到的数据版本。


一、undo日志

Undo:意为撤销或取消,以撤销操作为目的,返回指定某个状态的操作。
Undo Log:数据库事务开始之前,会将要修改的记录存放到 Undo 日志里,当事务回滚时或者数据库崩溃时,可以利用 Undo 日志,撤销未提交事务对数据库产生的影响。

1.undo日志的作用

undo日志只要有以下两个方面的作用:

  • 实现事务的原子性
    Undo Log 是为了实现事务的原子性而出现的产物。事务处理过程中,如果出现了错误或者用户执行了 ROLLBACK 语句,MySQL 可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。
  • 实现多版本并发控制(MVCC)
    Undo Log 在 MySQL InnoDB 存储引擎中用来实现多版本并发控制。事务未提交之前,Undo Log保存了未提交之前的版本数据,Undo Log 中的数据可作为数据旧版本快照供其他并发事务进行快照读。

2.undo日志的格式

InnoDB行格式

我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为行格式或者记录格式。

InnoDB一共有四种行格式:Compact、Redundant、Dynamic和Compressed行格式,行格式之间存在差异,但总体可以抽象成以下部分:
在这里插入图片描述

聚簇索引的记录除了会保存完整的用户数据以外,而且还会自动添加名为trx_id、roll_pointer的隐藏列,如果用户没有在表中定义主键以及UNIQUE键,还会自动添加一个名为row_id的隐藏列。

trx_id:某个对这个聚簇索引记录做改动的语句所在的事务对应的事务id(此处的改动可以是INSERT、DELETE、UPDATE操作)
roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息

undo日志格式

为了实现事务的原子性,InnoDB存储引擎在实际进行增、删、改一条记录时,都需要先把对应的undo日志记下来。一般每对一条记录做一次改动,就对应着一条undo日志。不同的操作(insert,delete,update)对应的undo日志有所差异,他们对应的undo日志格式展示如下。这里不会展开对每种日志字段进行说明,主要还是总结他们的差异和作用,以及跟行数据之间是如何通过roll_pointer属性进行联系。

insert操作的undo日志

在这里插入图片描述
这里需要注意的是,insert操作对应的undo日志没有roll_pointer属性的,因为该记录并没有更早的版本。

插入一条数据时,行记录跟undo日志的关系如下:
在这里插入图片描述

delete操作的undo日志

在这里插入图片描述
重点关注:

  1. delete操作的undo日志有roll_pointer属性
  2. delete操作是分为两个阶段来操作的:delete mark 阶段和 purge阶段,在delete mark阶段并不会真正地将数据从页面中清除,而是设置头信息为delete标识,在这个阶段同时将数据加入到版本链中。
    在这里插入图片描述

update操作的undo日志

在这里插入图片描述
更新时,有以下几种情况:
在这里插入图片描述
在这里插入图片描述

3. 事务id(trx_id)

一个事务可以是一个只读事务,或者是一个读写事务:

  1. 可以通过START TRANSACTION READ ONLY语句开启一个只读事务。
    在只读事务中不可以对普通的表(其他事务也能访问到的表)进行增、删、改操作,但可以对临时表做增、删、改操作。
  2. 可以通过START TRANSACTION READ WRITE语句开启一个读写事务,或者使BEGIN、START TRANSACTION语句开启的事务默认也算是读写事务。
    在读写事务中可以对表执行增删改查操作。如果某个事务执行过程中对某个表执行了增、删、改操作,那么InnoDB存储引擎就会给它分配一个独一无二的事务id

常见的场景:
在这里插入图片描述

上图操作中,虽然已经开启了一个事务,并且也对某张表进行了查询操作,但是因为没有对表进行增删改操作,这个时候,InnoDB引擎是不会分配事务id的。

二、MVCC

MVCC(Multi Version Concurrency Control)被称为多版本控制,是指在数据库中为了实现高并发的数据访问,对数据进行多版本处理,并通过版本链保证事务能看到自己应该看到的数据版本。
多版本控制很巧妙地将稀缺资源的独占互斥转换为并发,大大提高了数据库的吞吐量及读写性能。
如何生成的多版本?每次事务修改操作之前,都会在Undo日志中记录修改之前的数据状态和事务号,该备份记录可以用于其他事务的读取,也可以进行必要时的数据回滚。

MVCC最大的好处是读不加锁,读写不冲突。在读多写少的系统应用中,读写不冲突是非常重要的,极大的提升系统的并发性能,这也是为什么现阶段几乎所有的关系型数据库都支持 MVCC 的原因,不过目前MVCC只在Read CommitedRepeatable Read 两种隔离级别下工作。

1.版本链

创建一张表,并向数据表中添加一条记录

CREATE TABLE `user_mvcc` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

insert into user_mvcc (name) VALUES('Tom');
# 查询
mysql> select * from user_mvcc where id = 6;
+----+----------+
| id | name |
+----+----------+
|  6 | Tom      |
+----+----------+
1 row in set (0.11 sec)

之后两个事务id分别为100、200的事务对id=6这条记录进行UPDATE操作,操作流程如下:
在这里插入图片描述

每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表:
在这里插入图片描述

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

2.ReadView

使用READ COMMITTED或者REPEATABLE READ隔离级别,在当前事务中,必须保证以下两点:

  1. 能读到已经提交了的事务修改过的记录
  2. 不能读取到另一个事务已经修改的,但是尚未提交的记录。这个时候就要继续往上找该条记录的上一个版本,在依照一定的规则判断该版本的记录是否可见。这个规则,下面进行介绍。

所以核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。为此,设计InnoDB的大佬提出了一个ReadView的概念,这个ReadView中主要包含4个比较重要的内容:

  • m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表

  • min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。

  • max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值
    注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4

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

数据版本的可见性规则,就是基于数据的 row trx_id 和这个一致性视图的对比结果得到的。

在这里插入图片描述

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

  • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的trx_id属性值在ReadView的min_trx_idmax_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上面的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。

下面以REPEATABLE READ隔离级别下的MVCC演示说明。

3.REPEATABLE READ —— 在第一次读取数据时生成一个ReadView

还是以user_mvcc中id为6的数据举例:

mysql> select * from user_mvcc where id = 6;
+----+----------+
| id | name |
+----+----------+
|  6 | lisi     |
+----+----------+
1 row in set (0.08 sec)

有3个事务的执行顺序如下
在这里插入图片描述

下面通过以上的规则,说明为什么trx300两次查询的结果是lisi.
在这里插入图片描述

在trx100执行时间点5完成后,user_mvcc表中id=6的记录得到的版本链如下:

在这里插入图片描述
在时刻6,事务trx200执行更新其他表的操作,主要是为了分配事务id

时刻7查询1的分析如下:

  • 在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[100, 200]min_trx_id为100,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’jack’,该版本的trx_id值为100,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’Ally’,该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’lisi’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’lisi’的记录。

之后trx100事务提交,trx200执行更新user_mvcc表的id=6的记录,此时该条记录的版本链如下:
在这里插入图片描述
时刻10查询2的分析如下:

  • 因为当前事务的隔离级别为REPEATABLE READ,而之前在执行第一次查询时已经生成过ReadView了,所以此时直接复用之前的ReadView,之前的ReadView的m_ids列表的内容就是[100, 200]min_trx_id为100,max_trx_id为201,creator_trx_id为0。
  • 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’leilei’,该版本的trx_id值为200,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
  • 下一个版本的列name的内容是’jack’,该版本的trx_id值为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • 下一个版本的列name的内容是’Ally’,该版本的trx_id值为100,也在m_ids列表内,不符合,继续跳到下一个版本。
  • 下一个版本的列name的内容是’lisi’,该版本的trx_id值为80,小于ReadView中的min_trx_id值100,所以这个版本是符合要求。

也就是说两次SELECT查询得到的结果是相同,都是’lisi’,这就是可重复读的含义。

READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是:生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView。

4.快照读与当前读

有以下数据:

mysql> select * from t where id =1;
+----+---+
| id | c |
+----+---+
|  1 | 1 |
+----+---+
1 row in set (0.09 sec)

现在有以下操作场景:

在这里插入图片描述

那么此时,图中的SELECT1,SELECT2,SELECT3查询得到的c的值分别是多少?

根据前面的分析,很容易得到SELECT1,SELECT2得到c的值是1,而在SELECT3中,按照前面的分析应该得到c的值为2,但实际查询得到的结果是c = 3,那为什么会出现这种结果呢?

在 MVCC 并发控制中,读操作可以分为两类: 快照读(Snapshot Read)与当前读 (Current Read)

  • 快照读:读取的是记录的快照版本(有可能是历史版本),不用加锁。我们平时的select操作就是快照读
  • 当前读:读取的是记录的最新版本,并且当前读返回的记录,都会加锁,保证其他事务不会再并发修改这条记录。
    开启当前读的方式有:select… for update 或lock in share mode
    平时的insert/delete/update操作也是用的当前读的方式

所以图上的UPDATE2场景就是用到了当前读:更新数据都是先读后写;

UPDATE2更新之前,事务trx300执行了更新,此次该条数据的版本链中最新的数据为(1,2),当UPDATE2再进行更新时,因为是当前读,所以会拿到最新的值2进行操作,当UPDATE2执行完后,版本链中该条数据的c值就变成了3,所以在SELECT3中,查看到的c的值为3.

小结

所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。MVCC的实现是通过undo日志来完成的。

InnoDB 的行数据有多个版本,每个数据版本有自己的 row trx_id,每个事务或者语句有自己的一致性视图。普通查询语句是一致性读(快照读),一致性读会根据 row trx_id 和一致性视图确定数据版本的可见性。

  • REPEATABLE READ:只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView
  • READ COMMITTD:每一次进行普通SELECT操作前都会生成一个ReadView

更新数据操作都是先读后写的,读的都是版本链中最新的值,称为“当前读”(current read)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

qt-C++笔记之不使用ui文件纯C++构建时控件在布局管理器作用下的默认位置和大小实践

qt-C笔记之不使用ui文件纯C构建时控件在布局管理器作用下的默认位置和大小实践 code review! 文章目录 qt-C笔记之不使用ui文件纯C构建时控件在布局管理器作用下的默认位置和大小实践1.ChatGPT解释2.ChatGPT——resize()和move()详解3.默认大小和位置——示例运行一4.默认大小…

31 - MySQL调优之SQL语句:如何写出高性能SQL语句?

从今天开始,我将带你一起学习 MySQL 的性能调优。MySQL 数据库是互联网公司使用最为频繁的数据库之一,不仅仅因为它开源免费,MySQL 卓越的性能、稳定的服务以及活跃的社区都成就了它的核心竞争力。 我们知道,应用服务与数据库的交…

3D建模对制造企业的价值

除非你在过去几年一直躲在岩石下,否则你可能听说过“3D 建模”和“3D 渲染”这些术语。 但为什么这项技术如此重要,尤其是对于产品制造公司而言? 简而言之,它减少了项目时间和成本。 这为制造商提供了更多的设计试验空间。 未能利用 3D 建模技术的公司很快就会落后于竞争对…

MYSQL基础之【正则表达式,事务处理】

文章目录 前言MySQL 正则表达式MySQL 事务事务控制语句事务处理方法PHP中使用事务实例 后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:Mysql 🐱‍👓博主在前端领域还有很多知识和技术需要掌握,正在不…

P18 C++ 继承

目录 前言 01 不使用继承会让你多打很多无用的代码 02 继承 最后的话 前言 本期我们学习 C 面向对象编程中的继承。 面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做&#…

BC77 简单计算器(牛客)

#include <stdio.h> int main() {double a, b, d;//用来接收浮点数char c;//用来接受符号scanf("%lf %c %lf", &a, &c, &b);if (c || c - || c * || c /)//判断输入的运算符号不包括在&#xff08;、-、*、/&#xff09;范围内{switch (c)//根…

从0开始学习JavaScript--构建强大的JavaScript图片库

在现代Web开发中&#xff0c;图像是不可或缺的一部分&#xff0c;而构建一个强大的JavaScript图片库能够有效地管理、展示和操作图像&#xff0c;为用户提供更丰富的视觉体验。本文将深入探讨构建JavaScript图片库的实用技巧&#xff0c;并通过丰富的示例代码演示如何实现各种功…

因子分析例题(多元统计分析期末复习)

例一 设某客观现象可用 X {X} X( X 1 {X_1} X1​&#xff0c; X 2 {X_2} X2​&#xff0c; X 3 {X_3} X3​)’ 来描述&#xff0c;在因子分析时&#xff0c;从约相关阵出发计算特征值为 λ 1 {λ_1} λ1​1.754&#xff0c; λ 2 {λ_2} λ2​1&#xff0c; λ 3 {λ_3} λ3​…

transformers pipeline出现ConnectionResetError的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

(C++)string类的模拟实现

愿所有美好如期而遇 前言 我们模拟实现string类不是为了去实现他&#xff0c;而是为了了解他内部成员函数的一些运行原理和时间复杂度&#xff0c;在将来我们使用时能够合理地去使用他们。 为了避免我们模拟实现的string类与全局上的string类冲突(string类也在std命名空间中)&…

机器学习【04重要】pycharm中关闭jupyter服务器

直接关掉pycharm 不行 点红方块关闭 不行 我们曲线进行 我们的方法成功截图 实现全程不在服务器上操作 首先点击下图 点击退出&#xff0c;即可 查看端口

Java 注解在 Android 中的使用场景

Java 元注解有 5 种&#xff0c;常用的是 Target 和 Retention 两个。 其中 Retention 表示保留级别&#xff0c;有三种&#xff1a; RetentionPolicy.SOURCE - 标记的注解仅保留在源码级别中&#xff0c;并被编译器忽略RetentionPolicy.CLASS - 标记的注解在编译时由编译器保…

【Springboot系列】SpringBoot整合WebSocket,既然如此简单(含源码)

文章目录 前言&#xff1a;什么是WebSocket&#xff1f;Spring Boot中的WebSocket支持WebSocket和HTTP优劣势WebSocket的优势&#xff1a;1.实时性&#xff1a;2.较低的延迟&#xff1a;3.较小的数据传输量&#xff1a;4.更好的兼容性&#xff1a; HTTP的优势&#xff1a;1.简单…

从0开始学习JavaScript--JavaScript中的对象原型

JavaScript中的对象原型是理解该语言核心概念的关键之一。本文将深入探讨JavaScript对象原型的作用、使用方法以及与继承相关的重要概念。通过详细的示例代码和全面的讲解&#xff0c;将能够更好地理解和运用JavaScript对象原型&#xff0c;提高代码的可维护性和扩展性。 Java…

RHCE---给openlab搭建web网站

作业&#xff1a;请给openlab搭建web网站 网站需求&#xff1a; 1.基于域名 www.openlab.com 可以访问网站内容为 welcome to openlab!!! 2.给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料和缴费网站&#xff0c; 1、基于 www.openlab.com/student 网站访问学生信…

C#,《小白学程序》第十八课:随机数(Random)第五,方差及标准方差(标准差)的计算方法与代码

1 文本格式 /// <summary> /// 《小白学程序》第十八课&#xff1a;随机数&#xff08;Random&#xff09;第五&#xff0c;方差及标准方差&#xff08;标准差&#xff09;的计算方法与代码 /// 方差 SUM(&#xff08;Xi - X)^2 ) / n i0...n-1 X Average of X[i] ///…

如果客户端同时有ipv4和ipv6,浏览器是如何选择用哪种ip

在互联网协议&#xff08;IP&#xff09;的发展历程中&#xff0c;IPv4和IPv6是两种主要的版本。对于一个客户端来说&#xff0c;同时拥有IPv4和IPv6的能力是常见的情况。那么&#xff0c;当一个客户端同时具有IPv4和IPv6的能力时&#xff0c;浏览器是如何选择使用哪种IP进行通…

redis(Remote Dictionary Service) 底层数据结构

redis 底层数据结构 动态字符串SDS 优点 获取字符串长度的时间复杂度O(1) 支持动态扩容&#xff0c;减少内存分配次数 新字符串小于1M – 新空间为扩展后字符串长度的两倍 1 新字符串大于1M – 新空间为扩展后字符串长度 1M 1. 内存预分配 二进制安全&#xff08;记录了…

高并发系统:它的通用设计方法是什么?

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 我们知道&#xff0c;高并发代表着大流量&#xff0c;高并发系统设计的魅力就在于我们能够凭借自己的聪明才智设计巧妙的方案&#xff0c;从而抵抗巨大流量的冲击&#xff0c;带给用户更好的使用体验。这些方案好似能…

Kerberos 高可用配置和验证

参考 https://cloud.tencent.com/developer/article/1078314 https://mp.weixin.qq.com/s?__bizMzI4OTY3MTUyNg&mid2247485861&idx1&snbb930a497f63ac5e63ed20c64643eec5 机器准备 Kerberos主 ip-172-31-22-86.ap-southeast-1.compute.internal 7.common2.hado…