MySQL的事务隔离是如何实现的?

目录

从一个例子说起

快照读和当前读

 事务的启动时机和读视图生成的时刻

MVCC

隐藏字段

Undo Log回滚日志

Read View - 读视图

可重复读(RC)隔离级别下的MVCC

读提交(RR)隔离级别下的MCC

关于MVCC的一些疑问 

1.为什么需要 MVCC ?如果没有 MVCC 会怎样?

2.多版本,是在索引保存了该行数据的多个版本吗?

总结

从一个例子说起

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

MySQL有4个隔离级别:

  • 读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交:一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
  • 串行化:顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

用一个例子说明这几种隔离级别。假设数据表 T 中只有一列,其中一行的值为 1,下面是按照时间顺序执行两个事务的行为。

mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);

在不同的隔离级别中,事务 A 会有不同的返回结果,也就是图里面 V1、V2、V3 的返回值会不同。

  • 隔离级别是“读未提交”, 则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。
  • 隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以,V1还是1,而 V3 的值是 2。
  • 隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
  • 隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。

在实现上,数据库里面会创建一个视图read-view),访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。

这里需要先讲解下当前读和快照读 和 事务的启动时机和读视图生成的时刻

快照读和当前读

MySQL读取数据实际上有两种模式,分别是当前读和快照读。

快照读:普通的select语句(即是不加锁的select操作) 都是采用 快照读的模式。

当前读:数据修改的操作(update、insert、delete) 和select ... lock in share mode; select ... for update;都是采用 当前读的模式,对读取到的数据(索引记录)加锁来保证数据一致性,是读到最新的数据。

 事务的启动时机和读视图生成的时刻

在 MySQL 有两种开启事务的命令,分别是:

  • 第一种:begin/start transaction 命令;
  • 第二种:start transaction with consistent snapshot 命令;

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。

第一种启动方式,一致性视图是在执行第一个快照读语句时创建的;

第二种启动方式,一致性视图是在执行start transaction with consistent snapshot 时创建的。

说到视图read-view),那就会引出MVCC。而事务隔离就是通过MVCC来实现的

更加准确来说,实现事务隔离的方法是有两种:

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

所以,事务隔离是通过MVCC 和 加锁 实现的

那么,MVCC 用来实现哪几个隔离级别?

  • 隔离级别如果是读未提交的话,直接读最新版本的数据就行了,根本就不需要保存以前的版本,即是“读未提交”隔离没有视图概念。
  • 可串行化隔离级别事务都串行执行了(就是直接加锁避免并行访问),所以也不需要多版本。
  • 因此 MVCC 是用来实现读已提交和可重复读

MVCC

多版本并发控制(Multi-Version  Concurrency Control)是一种用来解决读-写冲突的无锁并发控制,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。

最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了 InnoDB 的并发度。

实现原理主要是依赖记录中的 三个隐藏字段undo日志 ,Read View 来实现的。

隐藏字段

innodb引擎保存的行数据是有三个隐藏字段的。

 具体的内容可以查看该文章MySQL的一行数据是如何存储的? 

 innodb引擎表 的聚簇索引保存的数据就是完整的行数据(即是上图的数据)。这里就主要是使用TRX_ID和ROLL_PTR。

DB_TRX_ID

最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID。

DB_ROLL_PTR

回滚指针,指向这条记录的上一个版本,即是指向指向 undo log 的指针。用于配合Undo Log,指向上一个版本。

 每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中。DB_ROLL_PTR是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。

Undo Log回滚日志

  • 这个是在增、改、删操作的时候产生的便于数据回滚的日志。
  • INSERT操作的时候,产生的回滚日志在事务提交后可被立即删除。而UPDATEDELETE操作的时候,产生的Undo Log日志不仅在进行数据回滚时需要,在进行快照读时也需要,所以不会立即被删除
  • 因为undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。

undo log 版本链 

用一个表做例子 ,建表和初始化语句如下。

mysql> CREATE TABLE `t` (`id` int NOT NULL,`age` int DEFAULT NULL,`name` varchar(10) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t values(1,30,'a30');

​ 不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。DB_ROLL_PTR回滚指针指向Undo Log数据地址形成一个链表。我们把这个链表称之为 版本链

Read View - 读视图

Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻(select ....),会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)。

已经弄清楚版本链后,而 readView 就是用来判断哪个版本对当前事务可见的。

readView中有4个概念:

  • m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务
  • min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
  • max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
  • creator_trx_id :指的是创建该 Read View 的事务的事务 id

知道版本链和读视图后,那如何通过读视图来判断哪个版本对当前事务是可见的呢?

代码中判断的逻辑如下:

最新版本开始沿着版本链逐渐寻找老的版本,如果遇到符合任一条件的版本就返回。

注意:在不同的隔离级别下快照读生成的ReadView规则不同:

  • read committed (读已提交):事务每次select时创建ReadView
  • repeatable read (可重复读):事务第一次select时创建ReadView,后续一直使用

而我们写sql语句后进行分析可见版本,是看不到min_trx_id和max_trx_id这些数据的。那我们用另一种方式来判断。

不知min_trx_id等数据的分析规则

一个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

  1. 版本未提交,不可见;
  2. 版本已提交,但是是在视图创建后提交的,不可见;
  3. 版本已提交,而且是在视图创建前提交的,可见。

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

可重复读(RC)隔离级别下的MVCC

用一个表做例子 ,建表和初始化语句如下。

mysql> CREATE TABLE `t` (`id` int NOT NULL,`age` int DEFAULT NULL,`name` varchar(10) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t values(30,30,'a30');

从上图时间顺序,事务2先启动,跟着是事务3,4,5依次启动。事务2对应的事务id是2,依次类推。

因为是RC隔离级别,所以每次select都会生成新的快照。

下面是每次提交事务生成的版本链&第一次快照读的ReadView

只分析事务5中的select,从最新版本开始沿着版本链逐渐寻找老的版本。

第一次select:

  • 当前最新版本的事务id是4,所以trx_id为4,不等于create_trx_id,所以不符合条件①。条件②,③,④也都不符合。所以事务id是4的版本不可见。
  • 跟着是老版本,事务id为3的版本,条件①不符合,条件②(3<3不符合)不符合。条件③(3<6),说明可能可以可见;条件④,3在[3,4,5]范围内,说明该版本在活跃事务列表中,未提交,不可见。所以事务id是3的版本不可见。
  • 事务id是2的版本,trx_id=2。条件①不符合,条件②符合(2<3)。所以事务id=2的版本可见

 查看上图的并发执行过程,对比事务5,发现事务2是已提交,因此此刻可以读取事务2提交过的数据。

第二次select也是这样分析,但注意的是在RC隔离级别下,是生成新的读视图的,这里还是按照上面的逻辑分析的,这里就不具体写了,留给读者分析。

用不知min_trx_id等数据的分析规则进行分析

第一次select:

  • 从最新版本开始查看,最新版本是事务id为4的,还没提交,属于情况1,不可见。
  • 到事务id为3的版本,还未提交,属于情况1,不可见。
  • 到事务id为2的版本,该版本已提交,而且是在视图创建前提交的(即是在事务5创建视图前),属于情况3,所以事务id为2的版本可见。

第二次select:

  • 从最新版本开始查看,最新版本是事务id为4的,还没提交,属于情况1,不可见。
  • 到事务id为3的版本,已提交,并且在视图创建前提交的(即是在事务创建视图前,第二次select),属于情况3,所以事务id为3的版本可见。

读提交(RR)隔离级别下的MCC

 这里的事务执行顺序和RC隔离级别的是一样的。

在RR隔离级别下,只是在事务中第一次快照读时生成ReadView,后续都是复用该 ReadView,那么既然ReadView都一样, ReadView的版本链匹配规则也一样, 那么最终快照读返 回的结果也是一样的。而且都是和RC隔离级别第一次select中的结果一样的。分析过程和RC级别的一样。

用不知min_trx_id等数据的分析规则进行分析

第一次select:和RC隔离级别的第一次select是一样的,事务id是2的版本可见。

第二次select:

  • 从最新版本开始查看,最新版本是事务id为4的,还没提交,属于情况1,不可见。
  • 到事务id为3的版本,已提交,但是是在视图创建后提交的(即是在事务5创建视图前,因为RR级别是延用第一次生成的视图),属于情况2,不可见。
  • 到事务id为2的版本,该版本已提交,而且是在视图创建前提交的(即是在事务5创建视图前),属于情况3,所以事务id为2的版本可见。

这样分析第一次select和第一次select读取的数据是一致的。

关于MVCC的一些疑问 

1.为什么需要 MVCC ?如果没有 MVCC 会怎样?

如果没有MVCC读写操作之间会有冲突。

假设一个场景:

事务A在执行中,此时事务B修改了记录1,还没提交;而此时事务A想要读取记录1。事务B还没提交,所以事务A无法提取到最新的记录1,不然就是脏读了。

那么事务A就是应该读取被事务B修改前的记录。但是记录1已被事务B修改了,那就只能用锁,用锁阻塞等到事务B的提交。这种实现就是基于锁的并发控制 ,Lock Based Concurrency Control(LBCC)。

这时,如果有多版本就好了,保存事务B修改记录1之前的版本数据。此时事务A就可以读取之前版本的数据,这样读写操作就不会阻塞,也不用加锁。所以说 MVCC 提高了事务的并发度,提升数据库的性能。

2.多版本,是在索引保存了该行数据的多个版本吗?

这个多版本说法只是为了便于理解或者说展现出来像多版本的样子而已。

 InnoDB 不会真的存储多个版本的数据,只是借助 undo log 记录每次写操作的反向操作,所以索引上对应的记录只会有一个版本(即最新版本)。只不过可以根据 undo log 中的记录反向操作得到数据的历史版本,所以看起来是多个版本。

总结

事务是在 MySQL 引擎层实现的,默认的 InnoDB 引擎支持事务。

MySQL InnoDB 引擎的默认隔离级别是 可重复读(RR),但不建议将隔离级别升级为串行化,因为这会导致数据库并发时性能很差。RR隔离级别是可以很大程度避免幻读现象(并不是完全解决),解决的方案有两种:

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

所以,事务隔离是通过MVCC 和 加锁 实现的。 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python学习笔记-Flask实现简单的抽奖程序(增加图片显示)

1.创建static文件夹,存放图片文件 2.hero列表数据更改为要抽奖的图片名 3.html中可以编写python语句,遍历hero列表内容渲染到表格中 4.在点击随机抽取后,可以获得名称,然后使用img标签,将获取的名称拼接到路径中 3.初始页面,访问127.0.0.1:5000/index 4.点击随机抽取后 5.py…

ResNeXt学习笔记

一、更新了block 二、将block中的卷积层分组 注意&#xff1a;对于block层数小于3的&#xff0c;没有太大作用

AIGC实战——GPT(Generative Pre-trained Transformer)

AIGC实战——GPT 0. 前言1. GPT 简介2. 葡萄酒评论数据集3. 注意力机制3.1 查询、键和值3.2 多头注意力3.3 因果掩码 4. Transformer4.1 Transformer 块4.2 位置编码 5. 训练GPT6. GPT 分析6.1 生成文本6.2 注意力分数 小结系列链接 0. 前言 注意力机制能够用于构建先进的文本…

System Verilog的接口、程序块与断言解析

接口、程序块与断言 1 接口 1.1 使用接口简化连接 // 接口 interface arb_if(input bit clk);logic [1:0] grant,request;logic rst; endinterface// 使用了简单接口的仲裁器 module arb (arb_if arbif);...always(posedge arbif.clk or posedge arbif.rst)beginif(arbif.rs…

opencv dnn模块 示例(25) 目标检测 object_detection 之 yolov9

文章目录 1、YOLOv9 介绍2、测试2.1、官方Python测试2.1.1、正确的脚本2.2、Opencv dnn测试2.2.1、导出onnx模型2.2.2、c测试代码 2.3、测试统计 3、自定义数据及训练3.1、准备工作3.2、训练3.3、模型重参数化 1、YOLOv9 介绍 YOLOv9 是 YOLOv7 研究团队推出的最新目标检测网络…

软件测试中的AI-为什么它在软件自动化测试中很重要?

通俗地说&#xff0c;人工智能&#xff08;AI&#xff09;是计算机科学的一个领域&#xff0c;它专注于使机器“智能化”。所谓智能&#xff0c;就是使系统能够像人类一样学习和做出决策。因此&#xff0c;人工智能机器将能够学习如何在特定情况下做出反应&#xff0c;然后根据…

【AIGC】重磅消息,GPT-4.5 Turbo将在6月发布?

2024 年 AI 辅助研发趋势 文章目录 强烈推荐GPT-4.5 Turbo竞争对手Anthropic的Claude 3谷歌的Gemini 1.5 Pro 总结强烈推荐专栏集锦写在最后 强烈推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击…

Cplex之新建一个项目并求解

cplex下载&#xff1a;Cplex安装教程与使用介绍-CSDN博客 链接&#xff1a;https://pan.baidu.com/s/1jD9Xved2aWPHtRd-_PLI5Q 提取码&#xff1a;n4og 补充b站上一个教程&#xff1a;快速学懂Cplex软件及其编程方法_哔哩哔哩_bilibili 一、建立并求解一个模型 step1 : 新…

【目标检测经典算法】R-CNN、Fast R-CNN和Faster R-CNN详解系列一:R-CNN图文详解

学习视频&#xff1a;Faster-RCNN理论合集 概念辨析 在目标检测中&#xff0c;proposals和anchors都是用于生成候选区域的概念&#xff0c;但它们在实现上有些许不同。 Anchors&#xff08;锚框&#xff09;&#xff1a; 锚框是在图像中预定义的一组框&#xff0c;它们通常以…

Python:函数的形参与实参

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 函数基本概念 在Python中&#xff0c;函数是一种将代码封装以进行重复使用的机制。它们允许你定义一段代码&#xff0c;以便在程序的多个位置调…

微信小程序开发系列(二十六)·小程序运行机制(启动、前后台状态、挂起、销毁)和小程序更新机制

目录 1. 小程序运行机制 1.1 启动 1.2 前台和后台状态 1.3 挂起 1.4 销毁 2. 小程序更新机制 1. 小程序运行机制 1.1 启动 小程序启动可以分为两种情况&#xff0c;一种是冷启动&#xff0c;一种是热启动。 冷启动&#xff1a;如果用户首次打开&#xff0c;或小…

OPPO后端二面,凉了!

这篇文章的问题来源于一个读者之前分享的 OPPO 后端凉经&#xff0c;我对比较典型的一些问题进行了分类并给出了详细的参考答案。希望能对正在参加面试的朋友们能够有点帮助&#xff01; Java String 为什么是不可变的? public final class String implements java.io.Seri…

阿里云服务器安全狗免费使用多引擎智能查杀引擎

云服务器具有按量付费、降低综合成本等诸多优势&#xff0c;受到很多企业的欢迎。 因此&#xff0c;目前使用的云服务器越来越多。 阿里云是目前云服务器中最具影响力的品牌&#xff0c;因此选择阿里云服务器的用户数量也是最多的。 那么阿里云服务器需要安装杀毒软件吗&#x…

Python学习:数据类型转换

数据类型转换 对数据内置的类型进行转换&#xff0c;数据类型的转换&#xff0c;一般情况下你只需要将数据类型作为函数名即可。 Python 数据类型转换可以分为两种&#xff1a; 隐式类型转换 - 自动完成显式类型转换 - 需要使用类型函数来转换 隐式类型转换 Python 会自动…

微信小程序一次性订阅requestSubscribeMessage授权和操作详解

一次性订阅&#xff1a;用户订阅一次发一次通知 一、授权 — requestSubscribeMessage Taro.requestSubscribeMessage({tmplIds: [], // 需要订阅的消息模板的id的集合success (res) {console.log("同意授权", res)},fail(res) {console.log(拒绝授权, res)}})点击或…

2001-2022年上市公司数字化转型程度指数测算数据(含原始数据+测算代码+计算结果)(无形资产衡量)

2001-2022年上市公司数字化转型程度指数测算数据&#xff08;含原始数据测算代码计算结果&#xff09; 1、时间&#xff1a;2001-2022年 2、指标&#xff1a;证券代码、证券简称、统计截止日期、是否发生ST或*ST或PT、、是否发生暂停上市、行业代码、行业名称、stkcd、year、…

mediapipe 实现姿态分析——举手检测

目录 人体姿态检测 效果展示 举手检测 行业应用 代码实现 代码分析 效果展示 代码修改&#xff0c;一只手举起即可 总结 啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦^_^啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦♪(^∇^*)啦啦啦…

【数据结构】二叉树---AVL树的实现

目录 一. 什么是AVL树 二. AVL树的结点结构定义 三. AVL树的动态平衡法 1. 左单旋转 --- RL(RotateLeft) 型调整操作 2. 右单旋转 --- RR(RotateRight) 型调整操作 3. 先左后右双旋转 --- RLR (RotateLeftRight) 型调整操作 4. 先右后左双旋转 --- RRL (RotateRightL…

Day32:安全开发-JavaEE应用Servlet路由技术JDBCMybatis数据库生命周期

目录 JavaEE-HTTP-Servlet&路由&周期 JavaEE-数据库-JDBC&Mybatis&库 思维导图 Java知识点&#xff1a; 功能&#xff1a;数据库操作&#xff0c;文件操作&#xff0c;序列化数据&#xff0c;身份验证&#xff0c;框架开发&#xff0c;第三方库使用等. 框架…

最新若依项目快速上手

最新若依项目快速上手 配套视频&#xff1a;若依项目快速上手视频 1. 下载源码 官网&#xff1a;https://ruoyi.vip/ 前端 git clone https://github.com/yangzongzhuan/RuoYi-Vue3.git后端 git clone https://gitee.com/y_project/RuoYi-Vue.git2. 数据库 创建数据库ry-vue…