一文带你搞懂 MySQL 事务

news2024/9/28 21:25:22

目录

1、事务的基本操作

2、事务的ACID属性

3、事务隔离级别

4、多版本并发控制( MVCC ) 

5、深入理解隔离级别


什么是事务?

事务就是一组 DML 语句组成,这些语句在逻辑上存在相关性,这一组 DML 语句要么全部成功,要么全部失败,是一个整体。MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。 事务就是要做的或所做的事情,主要用于处理操作量大,复杂度高的数据。
事务支持的版本
MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持。
查看数据库引擎
show engines;
事务提交方式
事务的提交方式常见的有两种:
  • 自动提交
  • 手动提交
查看事务提交方式
mysql> show variables like 'autocommit';
SET 来改变 MySQL 的自动提交模式 :
mysql> set autocommit=0;   
#SET AUTOCOMMIT=0 禁止自动提交
mysql> set autocommit=1;   
#SET AUTOCOMMIT=1 开启自动提交

1、事务的基本操作

start transaction;                         
# 开始一个事务begin 也可以,推荐 begin
savepoint save1;                        
# 创建一个保存点save1
rollback to save1(保存点);          
# 回滚到保存点save2,直接rollback ,回滚在最开始
commit;                                       
#提交事务

事务操作注意事项 

  • 如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交)
  • 如果一个事务被提交了(commit),则不可以回退(rollback)
  • 可以选择回退到哪个保存点

2、事务的ACID属性

一个完整的事务,不仅仅是简单的 sql 集合,还需要满足如下四个属性ACID
  • 原子性【Atomicity】一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过 一样
  • 致性【Consistency】在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作
  • 隔离性【Isolation】数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交read committed )、可重复读( repeatable read )和串行化( Serializable
  • 持久性【Durability】 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

下面我们通过一些实验来更好的理解ACID属性

原子性
##为了便于演示,我们将mysql的默认隔离级别设置成读未提交
set global transaction isolation level READ UNCOMMITTED;
##需要重启终端,进行查看
select @@transaction_isolation;

创建测试表
mysql> create table student(
id int primary key auto_increment,
name varchar(10) not null,
age int not null
);
 
mysql> begin;
mysql> insert into student(name, age) values('张三', 25);
mysql> insert into student(name, age) values('李四', 27);

##终端B查看表数据(如下图)

mysql> Aborted  ## ctrl + \ 异常终止MySQL

##终端B再次查看表数据

                  

 通过上面实验我们发现事务在执行过程中发生错误,会被回滚到事务开始前的状态,已经插入了两条数据但是最终表中却没有数据,说明一个事务要么全部完成,要么全部不完成,也就是原子性。

一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原 子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。

持久性

##前面操作不变,我们在异常终止前提交事务
mysql> commit
mysql> Aborted

如图:

                

 此时客户端崩溃,MySQL数据不会在受影响,已经持久化。

隔离性

  • MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行
  • 但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据
  • 数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
  • 数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别

一致性 (Consistency)
  • 事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而改未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态。因此一致性是通过原子性来保证的。
  • 其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是一致性是由用户决定的。
  • 其实原子性,隔离性、持久性最终都是为了保证一致性

3、事务隔离级别

  • 读未提交【Read Uncommitted: 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等,我们上面做实验,用的就是这个隔离性。
  • 读提交【Read Committed:该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。
  • 可重复读【Repeatable Read: 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。
  • 串行化【Serializable: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)
隔离级别如何实现:隔离,基本都是通过锁实现的,不同的隔离级别,锁的使用是不同的。常见有,表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key (GAP+ 行锁 ) 等。
查看与设置隔离性
select @@global.transaction_isolation;
#查看全局隔级别
select @@session.transaction_isolation;
#查看会话(当前)全局隔级别
set [session| global] transaction  isolation level {Read Uncommitted | Read Committed | Repeatable Read | Serializable}
#设置隔离级别语法
#设置全局隔离性,会话也会被影响
#注:不同MySQL版本,查看隔离级别略有差异。上面不行的话可以将transaction_isolation换成tx_isolation试试。
下面我们通过实验来更好的理解隔离性
读未提交 Read Uncommitted

 一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读(dirty read)。读未提交几乎没有加锁,虽然效率高,但是问题太多,严重不建议采用。

读提交 Read Committed

终端 A commit前后终端 B 读到的数据不一致那么就造成了,同一个事务内,同样的读取,在不同的时间段(依旧还在事务操作中!),读取到了不同的值,这种现象叫做不可重复读(non reapeatable read)。

可重复读【Repeatable Read

 可以看到,在终端B中,事务无论什么时候进行查找,看到的结果都是一致的,这叫做可重复读!

 
多次查看,发现终端 A 在对应事务中 insert 的数据,在终端 B 的事务周期中,也没有什么影响,也符合可重复的特点。但是,一般的数据库在可重复读情况的时候,无法屏蔽其他事务insert 的数据 ( 为什么?因为隔离性实现是对数据加锁完成的,而insert 待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题 ), 会造成虽然大部分内容是可重复读的,但是insert 的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读(phantom read) 。很明显, MySQLRR 级别的时候,是解决了幻读问题的
串行化【 Serializable

 

 总结:

  • 其中隔离级别越严格安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点
  • 不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了。幻读的重点在于新增:同样的条件, 1次和第2次读出来的记录数不一样
  • 说明: mysql 默认的隔离级别是可重复读,一般情况下不要修改
  • 上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的时候,即都没有commit的时候,影响会比较大
隔离级别脏读不可重复度幻读加锁读
读未提交【Read Uncommitted

不加锁
读提交Read Committed不加锁
可重复读【Repeatable Read不加锁
串行化【Serializable加锁

4、多版本并发控制MVCC  

数据库并发的场景      

  • -:不存在任何问题,也不需要并发控制
  • -:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • -:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
这里我们重点谈一谈读-写
多版本并发控制 MVCC )是一种用来解决 - 写 冲突 无锁并发控制 ,为事务分配单向增长的事务ID ,为每个修改保存一个版本,版本与事务 ID 关联,读操作只读该事务开始前的数据库的 快照。 所以 MVCC 可以为数据库解决以下问题
  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
  • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
理解 MVCC 需要知道三个前提知识:
  • 3个记录隐藏字段
  • undo 日志
  • Read View
3个记录隐藏列字段         
  • DB_TRX_ID 6 byte,最近修改( 修改/插入 )事务ID,记录创建这条记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR : 7 byte,回滚指针,指向这条记录的上一个版本(简单理解成,指向历史版本就行,这些数据一般在 undo log 中)
  • DB_ROW_ID : 6 byte ,隐含的自增 ID (隐藏主键),如果数据表没有主键, InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引
  • 实际还有一个删除 flag 隐藏字段 , 既记录被更新或删除并不代表真的删除,而是删除 flag 变了
undo 日志
  • undo log 可以简单理解成,就是 MySQL 中的一段内存缓冲区,用来保存日志数据的。
模拟 MVCC
现在有事务 10、11( 仅仅为了好区分 ) ,依次对 student 表中记录进行修改 (update)
  • 事务因为要修改,所以要先给该记录加行锁。
  • 修改前,先将该行记录拷贝到undo log中,所以,undo log中就有了一行副本数据(原理就是写时拷贝)。此时,新的副本,我们采用头插方式,插入undo log
  • 现在修改原始数据记录,并且修改原始记录的隐藏字DB_TRX_ID 为当前事务1ID。而原始记录的回滚指针 DB_ROLL_PTR 列,里面写入undo log中副本数据的地址,从而指向副本记录,既表示我的上一个版本就是它。
  • 事务10提交,释放锁。

 

这样,我们就有了一个基于链表记录的历史版本链。所谓的回滚,无非就是用历史数据,覆盖当前数据。 上面的一个一个版本,我们可以称之为一个一个的快照

一些思考问题
  • 上面是以更新(upadte)为例,如果是delete呢?一样的,别忘了删数据不是清空,而是设置flag为删除即可。也可以形成版本。
  • 如果是insert呢?因为insert是插入,也就是之前没有数据,那么insert也就没有历史版本。但是一般为了回滚操作,insert的数据也是要被放入undo log中,如果当前事务commit了,那么这个undo log 的历史insert记录就可以被清空了。
  • 那么select呢?首先,select不会对数据做任何修改,所以,为select维护多版本没有意义。

不过此时有个问题,就是:select读取,是读取最新的版本呢?还是读取历史版本?​​​​​​

  • 当前读:读取最新的记录,就是当前读。增删改,都叫做当前读,select也有可能当前读,比如:select lock in share mode(共享锁), select for update 
  • 快照读:读取历史版本(一般而言),就叫做快照读。
  • 我们可以看到,在多个事务同时删改查的时候,都是当前读,是要加锁的。那同时有select过来,如果也要读取最新版(当前读),那么也就需要加锁,这就是串行化。
  • 但如果是快照读,读取历史版本的话,是不受加锁限制的。也就是可以并行执行!换言之,提高了效率,即MVCC的意义所在。

5、深入理解隔离级别

那么,是什么决定了,select 是当前读,还是快照读呢?隔离级别 !
那为什么要有隔离级别呢?
  • 事务都是原子的。所以,无论如何,事务总有先有后。但是经过上面的操作我们发现,事务从begin->CURD->commit,是有一个阶段的。也就是事务有执行前,执行中,执行后的阶段。但,不管怎么启动多个事务,总是有先有后的。
  • 那么多个事务在执行中,CURD操作是会交织在一起的。那么,为了保证事务的“有先有后,是不是应该让不同的事务看到它该看到的内容,这就是所谓的隔离性与隔离级别要解决的问题。
那么,如何保证,不同的事务,看到不同的内容呢?也就是如何如何实现隔离级别?
Read View
  • Read View就是事务进行快照读操作的时候生产的 读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
  • Read View MySQL 源码中,就是一个类,本质是用来进行可见性判断的。 即当我们某个事务执行快照读的时候,对该记录创建一个 Read View 读视图,把它比作条件,用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的 undo log 里面的某个版本的数据。

下面是一个简化的ReadView 结构 ​​​​​​

我们在实际读取数据版本链的时候,是能读取到每一个版本对应的事务ID的,即:当前记录的 DB_TRX_ID 我们现在已经知道了当前快照读的 ReadView 和 版本链中的某一个记录的 DB_TRX_ID 。所以现在的问题就是,当前快照读,应不应该读到当前版本记录。我们用图来说明。

 整体流程

假设当前有条记录:

事务操作 

 事务2 对某行数据执行了 快照读 ,数据库为该行数据生成一个 Read View 读视图

我们的事务 2 在快照读该行记录的时候,就会拿该行记录的 DB_TRX_ID 去跟 up_limit_id,low_limit_id 和活跃事务ID 列表 (trx_list) 进行比较,判断当前事务 2 能看到该记录的版本。

  

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

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

相关文章

基于OpenCV提供的人脸识别算法LBPH实现人脸识别

本文通过学习LBPH人脸识别算法,简要了解人脸识别技术的原理,实现人脸采集、训练人脸模型实现人脸识别。 文章目录一、 LBPH人脸识别算法概述二、 人脸识别技术原理三、 关键模块四、 实验准备1. 第三方库2. 新建相关文件夹3. 实验环境五、 人脸采集与检测…

【自学Java】Java开发环境搭建

Java开发环境搭建 Java开发环境搭建 Java 的开发环境主要使用 JDK。我们这边介绍的 JDK 版本是 1.8。我们可以直接从 Oracle 官网上面下载。JDK 下载地址: https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html如下图: 由于在…

如何使用css实现三角形?

​ 一、前言 ​ 通常情况下,我们会使用图片或者svg去完成三角形效果图,但如果单纯使用css如何完成一个三角形呢? 实现过程似乎也并不困难,通过边框就可完成 二、实现过程 在以前也讲过盒子模型,默认情况下是一个矩形…

Faster RCNN网络源码解读(Ⅳ) --- Faster R-CNN主体代码执行流程解析

目录 一、Faster R-CNN框架图 二、结合代码 (faster_rcnn_framework.py) 2.1 FasterRCNNBase类 2.2 FasterRCNN类 一、Faster R-CNN框架图 我们获取一张图片后将其输入特征提取网络Backbone中得到特征图,将特征图输入到RPN中得到一系列的…

Maven的基本使用方法

Maven Maven是专门用于管理和构建Java项目的工具,它的主要功能有: 提供了一套标准化的项目结构 提供了一套标准化的构建流程(编译,测试,打包,发布……) 提供了一套依赖管理机制 标准化的项…

【JavaEE】Linux

努力经营当下,直至未来明朗! 文章目录一、Linux概述二、 云服务器安装Linux环境 Xshell登录三、Linux中常用指令介绍1. ls2. pwd 查看当前路径对应的绝对路径3. cd 切换当前的工作目录4. touch:创建一个空文件5. cat: 显示文件内…

23 种设计模式总结

title: 23 种设计模式总结 date: 2022-12-30 16:53:46 tags: 设计模式 categories:设计模式 cover: https://cover.png feature: false 文章目录1. 创建型1.1 单例模式(Singleton Design Pattern)1.1.1 概述与实现1.1.2 多例1.2 工厂模式(Fa…

零信任与 K8s 环境实践

零信任的热度自然吸引了大量市场上的关注。尽管如此,零信任并不只是一个空洞的术语:它代表了对未来安全性的一些深刻变革的想法。那么,零信任具体是什么,为什么它会突然变得如此重要?零信任对 Kubernetes 用户来说具体…

【数据结构】顺序表(线性表)的实现

目录 一、什么是顺序表? 二、顺序表的动态实现 1、顺序表初始化 2、顺序表打印 3、顺序表检查空间 4、顺序表尾插 5、顺序表尾删 6、顺序表头插 7、顺序表头删 8、顺序表指定位置插入 9、顺序表指定位置删除 10、顺序表查找 11、顺序表销毁 三、源代码 1、SeqList.h…

hnu社交网络作业1

前言:上的是林剑新老师的课程,还是比较有意思的,此博客用来记录作业的学习情况,答案为老师提供的 一、对于图 1,请回答以下问题,并给出相应的计算过程: (1) 计算图 G 中每个顶点的 closeness 中…

Java重点源码回顾——HashMap1.7

1. 概述 public class HashMap<K,V>extends AbstractMap<K,V>implements Map<K,V>, Cloneable, SerializableHashMap在我们的日常使用中非常多&#xff0c;所以今天来阅读下它的源码&#xff0c;了解它具体的设计思想&#xff0c;能够帮助我们扩宽视野。 H…

vmware vcp证书怎么考?vmware vcp证书通过率如何

可为您提供行业领先的虚拟化技术培训和认证服务&#xff0c;这些认证不但会考察您的知识掌握情况和经验水平&#xff0c;还将与您的实际工作职责挂钩。VMware认证按照不同解决方案划分&#xff0c;可分为四条路径&#xff1a;数据中心虚拟化、网络虚拟化、云计算管理和自动化、…

微信小程序项目转uniapp踩坑日记

本文目录一、前言二、转换方式三、后语四、其他&#xff1a;node报错1、包默认C盘存放&#xff0c;而不是安装目录E盘2、正确的环境变量添加3、npm install 命令报错4、npm install -g express报错没有权限一、前言 由于想要把之前完成的微信小程序项目转换成uniapp项目&#…

git入门指南

文章目录Git入门指南前言什么是版本控制系统&#xff08;VCS&#xff09;版本控制系统Git1、概述2、目前比较流行的Git和SVN&#xff0c;区别是什么3、Git安装4、Git的工作区、暂存区、本地仓库、远程仓库5、git的分支6、git的标签7、 实际操作下git常用命令准备操作git cloneg…

Kafka Cluster 扩容 添加副本 重分配分区

Kafka Cluster 扩容 针对kafka集群&#xff0c;可以通过向群集添加新节点来扩展群集。新节点将仅服务于新主题或新分区&#xff0c;现有分区将不会自动重新平衡以使用新节点。如果需要对现有的TOPIC进行重新分配分区&#xff0c;需要运维人员手动进行干预。今天学习下如何对已…

CAPL学习之路-测试功能集函数(故障注入函数)

TestDisableMsg 禁止发送消息,除非调用函数TestSetMsgEvent 使用TestEnableMsg重新启用消息。此函数影响分配CANoe交互层或CANopen仿真的仿真节点 这个函数可以在测试用例中控制Simulation Setup界面仿真节点报文的发送与停止 testcase TCExample() {testDisableMsg(LightSt…

Linux模块代码、编译、加载、卸载一条龙

最近要写一个Linux的内核模块&#xff0c;记录一下内核模块的代码编写、编译、加载和卸载的基本流程&#xff0c;以作备忘&#xff0c;也希望能帮到有需要的同学。 模块代码 //代码来自https://yangkuncn.cn/kernel_INIT_WORK.html //init_works.c #include <linux/kernel…

Docker-compose快速部署PostgreSQL

Docker-compose快速部署PostgreSQL&#xff1a; 利用docker-compose编排工具部署&#xff1a; docker-compose.yml 文件 version: "3.1" services:postgresql:image: postgres:12-alpinecontainer_name: postgresqlenvironment:POSTGRES_DB: postgresPOSTGRES_USE…

Python--数据容器总结

一、数据容器的分类 数据容器可以从一下视角进行简单的分类&#xff1a; 是否支持下标索引 支持&#xff1a;列表、元组、字符串 --序列类型不支持&#xff1a;集合、字典 --非序列类型是否支持重复元素 支持&#xff1a;列表、元组、字符串 --序列类型不支持&#xff1a;集…

自动生成单测代码插件Squaretest

今天来介绍一款工具Squaretest&#xff0c;它是一款自动生成单元测试的插件&#xff0c;会用到它也是因为最近公司上了代码质量管控的指标&#xff0c;会考评各个项目的单元测试覆盖率&#xff0c;以及sonar扫描出来的各种问题。 很多老项目老代码&#xff0c;或者着急交付的项…