MySQL中InnoDB的事务隔离

news2025/2/24 16:38:51

文章目录

  • 前言
  • 一、事务介绍
  • 二、事务的四大特性
  • 三、事务的隔离性
  • 四、事务隔离的实现


前言

我们在实际开发中,执行某个业务,肯定不是简单的操作某一句SQL语句,而是多条SQL语句。那么这多条SQL语句必须是全部成功执行,或者全部失败。才能保证业务逻辑的正确。因此这便引出了事务。接下来我们对MySQL的事务进行深入的了解学习


一、事务介绍

事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在MySQL中,事务支持是在引擎层实现的,并且也不是所有的引擎都支持事务的,比如MyISAM引擎就不支持事务,这也是MyISAM被InnoDB取代的重要原因之一。
本文将会以InnoDB为例,将对MySQL对事务的支持进行深入学习。

对MySQL有基本了解的,提到事务,肯定会想到ACID(Atomicity、Consistency、lsolation、Durability,即原子性、一致性、隔离性、持久性)。
接下来我们先对这四大特性进行一个简单了解

二、事务的四大特性

  1. 原子性:一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样,就好比买一件商品,购买成功时,则给商家付了钱,商品到手;购买失败时,则商品在商家手中,消费者的钱也没花出去。
  2. 一致性:是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。 比如,用户A和用户B在银行分别有800元和600元,总共 1400 元,用户 A 给用户 B 转账 200 元,分为两个步骤,从 A 的账户扣除 200 元和对 B 的账户增加 200 元。一致性就是要求上述步骤操作后,最后的结果是用户 A 还有 600 元,用户 B 有 800 元,总共 1400 元,而不会出现用户 A 扣除了 200 元,但用户 B 未增加的情况(该情况,用户 A 和 B 均为 600 元,总共 1200 元)。
  3. 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的。也就是说,消费者购买商品这个事务,是不影响其他消费者购买的。
  4. 持久性:事务处理结束后,对数据的修改就是永久的,即使系统故障也不会丢失。

了解了事务这四大特性之后,那么这四大特性分别是怎么实现的呢,这里简答介绍下:
以InnoDB引擎为例,来介绍如何保证事务的这四个特性的

  • 持久性是通过redo log(重做日志)来保证的
  • 原子性是通过undo log(回滚日志)来保证的
  • 隔离性是通过MVCC(多版本并发控制)或锁机制来保证的
  • 一致性则是通过持久性+原子性+隔离性来保证

而这其中最重要,也是经常被考查的就是隔离性。接下来重点学习下事务的隔离性

三、事务的隔离性

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

在介绍隔离级别之前,我们先介绍下这三个可能出现的问题:

  • 脏读:如果一个事务读到了另一个未提交事务修改过的数据,就意味着发生了脏读现象;(毕竟未提交过的事务都有随时可能发生回滚操作)
  • 不可重复读:在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了不可重复读现象(可能是其他事务修改了这条数据,并进行了提交)前后读取的数据不一致
  • 幻读:在一个事务内多次查询某个符合查询条件的记录数量,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了幻读现象(可能是其他事务增添或者删除了记录并进行了提交)前后读取的记录数量不一致

为了解决这些可能出现的问题,便设计处了事务的隔离级别,其中包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable)。

在介绍这几个隔离级别之前,不知道读者是否有个疑问,既然知道了事务存在这些可能发生的问题,我们直接设计出一个隔离级别,一起把这三个可能出现的问题解决了不就行了吗,为什么还要设计出这么多个隔离级别啊。

为此,我们先来解答一下这个问题。那是因为隔离级别有高低之分,隔离级别越高,性能越低。因此很多时候,我们都要在二者之间寻找一个平衡点。这几个隔离级别隔离水平高低排序如下:
在这里插入图片描述

解决了这个疑惑之后,接下来为各个隔离级别进行介绍:

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

为了彻底搞清楚这几个隔离级别,这里用一个例子进行说明。

假设数据表T中只有一列,其中一行的值为1,如下SQL语句:

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看到。所以,V3的值也是2
  • 若隔离级别是“可重复读”,则V1、V2是1,V3是2。之所以V2还是1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的
  • 若隔离级别是 “串行化”,则在事务B执行“将1改成2”的时候,会被锁住(被事务A的读锁锁住)。直到事务A提交后,事务B才可以继续执行。所以从A的角度看,V1、V2值是1,V3的值是2。

通过例子了解了事务的隔离性,那么我们对每个隔离级别可以解决的问题应该也清楚了。
为了清楚记忆,构建下图(针对不同的隔离级别,并发事务可能发生的现象):
在这里插入图片描述
通过上述,可以清楚认识到在不同的隔离级别下,数据库的行为是不同的。既然开发者设计出来,肯定有它的适用场景,根据实际开发的业务判断即可。

如下,举个“可重复读”的场景:数据校对逻辑的案例
假设在管理一个个人银行账号表。一个表存了每个月月底的余额,一个表存了账单明细。这时候要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。这时肯定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。
这时候使用“可重复读”隔离级别就很方便

了解了事务的隔离性,接下来我们再深入学习以下,看看这事务的隔离性是怎么实现的

四、事务隔离的实现

这里先总体说下实现。在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。

总体了解了之后,我们再来学习下事务隔离具体的实现,这里以“可重复读”为具体说明:

在MySQL中,每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

假设一个值从1被按顺序改成2、 3、 4,则在回滚日志里就会有类型下面的记录:
在这里插入图片描述
从上图,我们看见了一个新的东西:Read View。没接触过的肯定不知道。接下来我们先介绍下它。

如下图所示:
在这里插入图片描述
Read View的四个重要字段:

  • 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。

知道了Read View的字段,还要知道聚簇索引记录中的两个隐藏列。
如下图所示,每个行记录中包括的两个隐藏列:
在这里插入图片描述
对于InnoDB存储引擎的数据库表,聚簇索引记录包含的隐藏列解释如下:

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

在创建Read View后,可以将记录中的trx_id划分为这三种情况:
在这里插入图片描述

了解了这之后,接着看开头把表数据的值从1改成2,然后3、 4。那么当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的read-view。从上面的数据更新图可以看出,在视图A、B、C里面,这一个记录的值分别是1、2、 4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于上面数据更新图里,对于read-view A,要得到1,就必须将当前值依次执行图中所有的回滚操作得到。

这是不知道你发现问题了没有。设想,如果我们数据更新的比较频繁,那么回滚日志中的数据不能一直都保留吧,那么什么时候才会删除记录呢?

系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的read-view的时候。

此时在使用时有个建议:
尽量不要使用长事务,因为长事务就意味着系统里面会存在很老的事务视图。并且这些事务随时可能访问数据库里面任何数据,所以这个事务提交之前,数据库里面它可能用到回滚记录都必须保留,这就会导致大量占用存储空间。

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

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

相关文章

[L1 - 10分合集]吃鱼还是吃肉

L1-063 吃鱼还是吃肉 分数 10 作者 陈越 单位 浙江大学 题目: 国家给出了 8 岁男宝宝的标准身高为 130 厘米、标准体重为 27 公斤;8 岁女宝宝的标准身高为 129 厘米、标准体重为 25 公斤。 现在你要根据小宝宝的身高体重,给出补充营养的建议…

最近发现关于计算机网络的1个秘密

最近闲着没啥事翻开之前谢希仁老师第7版的《计算机网络》这本书,结果发现了1个惊天的秘密。 首先是互联网与互连网的区别,一般我们常说的互联网是Internet,是指因特网,其起源于阿帕网。或许很多读者看到这里就觉得有什么秘密可言,不都是常识了吗?看你大惊小怪的。 我们不妨看看…

spring cloud、gradle、父子项目、微服务框架搭建---rabbitMQ延时队列(七)

总目录 https://preparedata.blog.csdn.net/article/details/120062997 文章目录总目录一、rabbit延时插件下载二、rabbit插件安装三、项目中配置延时队列四、定义消息通道五、生成消息六、监听消息,进行消费延时队列的配置是对上片文章的延伸扩展 https://prepare…

paddledetection推理代码结构

https://github.com/PaddlePaddle/PaddleDetection/blob/release%2F2.5/deploy/pipeline/README.mdhttps://github.com/PaddlePaddle/PaddleDetection/blob/release%2F2.5/deploy/pipeline/README.md GitHub - leeguandong/Xiaobao: videoclip,视频剪辑应用videocl…

Go 1.19.3 error原理简析

Go error是一个很痛的话题(真心难用) 标准库 error 的定义 // The error built-in interface type is the conventional interface for // representing an error condition, with the nil value representing no error. type error interface {Error() string }error 是一个…

windows10安装wireshark

win10安装wireshark并使用windows10安装wireshark下载WIRESHARK下载Win10Pcapwindows10安装wireshark 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知…

javaEE 初阶 — JUC(java.util.concurrent) 的常见类

文章目录1. Callable 接口1.1 Callable 的用法2. ReentrantLock2.1 ReentrantLock 的缺陷2.1 ReentrantLock 的优势3. 原子类4. 信号量 Semaphore5. CountDownLatch6. 相关面试题1. Callable 接口 类似于 Runnable 一样。 Runnable 用来描述一个任务,描述的任务没有…

【Spring源码】21. 关于循环依赖的N个问题

完成了applyMergedBeanDefinitionPostProcessors()方法,后面有一段关于判断Bean是否需要提前曝光的逻辑(如下图红框框中部分)在这段逻辑中涉及到了著名的循环依赖,提到循环依赖基本必讲三级缓存,好吧,这篇就…

CANOpen中SDO和PDO的COB-ID理解

CAN 总线是一种串行通信协议,具有较高的通信速率的和较强的抗干扰能力,可以作为现场总线应用于电磁噪声较大的场合。由于 CAN 总线本身只定义ISO/OSI 模型中的第一层(物理层)和第二层(数据链路层)&#xf…

(8)go-micro微服务Mysql配置

文章目录一 gorm介绍二 gorm安装1.1 下载依赖1.2 使用MySQL驱动三 CURD操作1. 查询1.1 单行查询1.2 多行查询2. 插入数据3. 更新数据4. 删除数据四 初始化连接五 使用六 最后一 gorm介绍 Go语言中的database/sql包提供了保证SQL或类SQL数据库的泛用接口,并不提供具…

redis: jedis连接超时(需要手动注入连接超时检测的配置)

相关版本说明 服务端: redis_version: 6.2.8 客户端: springBoot: 2.7.7 jedis: 3.8.0 问题 偶发redis连接超时,刷新就又好了,服务日志错误信息如下: JedisConnectionException: Unexpected end of stream.原因 …

Linux利用httpd搭建局域网yum源

本例环境:vmwareworkstation16 proCentOS7.9 mast节点:192.168.195.110 用于配置httpd并发布本地yum源 node节点:192.168.195.111 用于验证mast节点的yum源是否可用 思路:1.在mast节点挂载/上传镜像后配置本地yum源 2.利用本…

JSP三种脚本

脚本可以编写Java语句、变量、方法或表达式。 1.普通脚本 语法: <% Java代码%> <% page contentType"text/html;charsetUTF-8" language"java" %><html><head> <title>Title</title></head><body>&l…

对u盘的分区进行删除和格式化

一、说明 当usb盘&#xff0c;或者SD卡用作启动盘后&#xff0c;将出现多个盘符、多个分区&#xff1b;若将此盘重新当文件盘&#xff0c;需要删除以前的分区&#xff0c;并重新格式化后&#xff0c;才能使用。 二、使用Diskpart在Windows 10中对USB进行分区删除 2.1 尝试磁盘…

重启之后,台式机网络不能连接怎么办

目录 1.问题 2.排查过程 3.心得 1.问题 前天电脑意外断电后,再启动发现网络变成了未连接状态.查看本地连接显示已启动,但IPv4和IPv6未连接.当时做了一些尝试,没有收到效果,直到今天问题才得以解决. 2.排查过程 Windows网络诊断为:DNS服务器未响应.后来花了一部分时间在DNS…

ruoyi-vue集成magic-api(一)

ruoyi虽然带了强大的代码生成器&#xff0c;面对比较通用的CRUD还是游刃有余的&#xff0c;但在项目开发阶段&#xff0c;需求总是经常变化的&#xff0c;数据结构和逻辑也经常变化&#xff0c;我们需要的是快速验证功能逻辑&#xff0c;代码生成器可帮不上忙&#xff0c;每次需…

一、java编写登录功能

java编写登录功能 文章目录java编写登录功能前言编程学习记录一、登录逻辑简述二、代码实现1.创建USER表2.前端代码3.创建User类4.创建LoginServlet类5.创建JDBCUtils类6.创建UserDao类7.创建FailServlet类9.创建SuccessServlet 类11.配置tomcat 服务12.启动服务前言 编程学习…

SpringCloud Netfllix复习之Hystrix

文章目录写作背景Hystrix是什么Hystrix的核心功能上手实战RestTemplate整合HystrixOpenFeign整合HystrixOpenFeign与Hystrix整合的各种参数如何配置&#xff1f;源码验证基于HystrixCommand注解实现熔断源码分析初始化资源线程池的源码OpenFeign与Hystrix整合执行请求的源码写作…

Java多线程:创建多线程的“四种“ 方式

Java多线程&#xff1a;创建多线程的"四种" 方式 每博一文案 白马笑西风写道&#xff1a;江南有杨柳&#xff0c;有燕子&#xff0c;金鱼......汉人中有的是英俊勇武的少年&#xff0c;倜傥潇洒的少年...... 但这个美丽的姑娘就像故高昌国人那样固执&#xff1a;&qu…

buctoj-2023寒假集训-进阶训练赛(八)

问题 A: 分离出整数n从右边数第k个数字&#xff0c;递归实现 题目描述 在程序中定义一函数digit(n,k)&#xff0c;它能分离出整数n从右边数第k个数字。 输入 正整数n和k。 输出 第k个数字(若不存在则输出0&#xff09; 样例输入 31859 3 样例输出 8 #include<bits/stdc.h&g…