当前位置:网站首页>MySQL【ACID+隔离级别+ redo log + undo log】
MySQL【ACID+隔离级别+ redo log + undo log】
2022-04-23 06:00:00 【0oIronhide】
本文整理自尚硅谷MySQL数据库教程天花板
写在前面:
简单的思维导图:
并发的事务会导致数据不一致问题(脏写脏读等) ->
引申出四种隔离级别(解决问题的方案) ->
隔离级别由事务日志+锁+MVCC共同实现(解决问题的工具)
|
锁和MVCC不在此篇文章介绍
文章目录
MySQL数据库事务
事务的ACID特性
-
原子性(atomicity): 原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚。 通过undo log来保证一个事务能够成功回滚。
-
一致性(consistency): 根据定义,一致性是指事务执行前后,数据从一个 合法性状态 变换到另外一个 合法性状态 。合法性状态说的通俗一点,这状态是由你自己来定义的(比如满足现实世界中的约束)。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的。
举例1:A账户有200元,转账300元出去,此时A账户余额为-100元。你自然就发现了此时数据是不一致的,为什么呢?因为你定义了一个状态,余额这列必须>=0。
举例2:A账户200元,转账50元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加。你也知道此时数据是不一致的,为什么呢?因为你定义了一个状态,要求A+B的总余额必须不变。
举例3:在数据表中我们将姓名字段设置为唯一性约束,这时当事务进行提交或者事务发生回滚的时候,如果数据表中的姓名不唯一,就破坏了事务的一致性要求。 -
隔离型(isolation): 事务的隔离性是指一个事务的执行不能被其他事务干扰 ,即一个事务内部的操作及使用的数据对其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性通过锁与MVCC来保证。
-
持久性(durability): 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的 ,接下来的其他操作和数据库 故障不应该对其有任何影响。持久性是通过重做日志(redo log)来保证的。
事务的状态
我们现在知道事务是一个抽象的概念,它其实对应着一个或多个数据库操作,MySQL根据这些操作所执行的不同阶段把事务大致划分成几个状态:
- 活动的(active) 事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。
- 部分提交的(partially committed) 当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
- 失败的(failed) 当事务处在 活动的 或者 部分提交的 状态时,可能遇到了某些错误(数据库自身的错误、操作系统 错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
- 中止的(aborted) 如果事务执行了一部分而变为失败的状态,那么就需要把已经修改的事务中的操作还原到事务执行前的状态。换句话说,就是要撤销失败事务对当前数据库造成的影响。我们把这个撤销的过程称之为回滚 。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事 务处在了中止的状态。
- 提交的(committed) 当一个处在 部分提交的 状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。
如何使用事务
使用事务有两种方式,分别为 显式事务 和 隐式事务 。
-
使用显示事务:
步骤1: START TRANSACTION 或者 BEGIN ,作用是显式开启一个事务。
mysql> BEGIN; #或者 mysql> START TRANSACTION;START TRANSACTION 语句相较于 BEGIN 特别之处在于,后边能跟随几个修饰符 :
- READ ONLY :标识当前事务是一个 只读事务 ,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。
- READ WRITE (默认) :标识当前事务是一个读写事务 ,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据。
- WITH CONSISTENT SNAPSHOT :启动一致性读。
步骤2:一系列事务中的操作(主要是DML,不含DDL)
步骤3:提交事务 或 中止事务(即回滚事务)
# 提交事务。当提交事务后,对数据库的修改是永久性的。 mysql> COMMIT; # 回滚事务。即撤销正在进行的所有没有提交的修改 mysql> ROLLBACK; # 将事务回滚到某个保存点。 mysql> ROLLBACK TO [SAVEPOINT] -
隐示事务:
隐式事务 MySQL中有一个系统变量 autocommit
SHOW VARIABLES LIKE 'autocommit'; --默认是开启自动提交会隐式提交事务的情况:

ps:在Hibernate和MyBatis等持久层框架中,处理DML语句的时候会自动设置autocommit=0;如果DML中不进行手动提交事务,那么最后事务就会进行回滚。
事务隔离级别
事务有隔离性,理论上在某个事务对某个数据进行访问 时,其他事务应该进行排队,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样并发性不高 ,我们既想保持事务的隔离性,又想让服务器在处理访问同一数据的多个事务时性能尽量高 ,那就看二者如何权衡取舍了。
数据并发问题
针对事务的隔离性和并发性,我们怎么做取舍呢?先看一下访问相同数据的事务在不保证串行执行 (也就是执行完一个再执行另一个)的情况下可能会出现哪些问题:
-
脏写( Dirty Write ) 对于两个事务 Session A、Session B,如果事务Session A 修改了另一个未提交事务Session B修改过 的数据,那就意味着发生了脏写
-
脏读( Dirty Read ) 对于两个事务 Session A、Session B,Session A 读取了已经被Session B 更新但还没有被提交的字段。 之后若 Session B 回滚 ,Session A 读取的内容就是临时且无效的。
-
不可重复读( Non-Repeatable Read ) 对于两个事务Session A、Session B,Session A 读取了一个字段,然后 Session B 更新了该字段。 之后 Session A 再次读取同一个字段, 值就不同了。那就意味着发生了不可重复读。
我们在Session B中提交了几个隐式事务 (注意是隐式事务,意味着语句结束事务就提交了),这些事务都修改了studentno列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看到最新的值,这种现象也被称之为不可重复读 。
-
幻读( Phantom ) 对于两个事务Session A、Session B, Session A 从一个表中读取 了一个字段, 然后 Session B 在该表中 插 入 了一些新的行。 之后,如果 Session A 再次读取同一个表,就会多出几行。那就意味着发生了幻读。
Session A中的事务先根据条件 studentno > 0这个条件查询表student,得到了name列值为’张三’的记录; 之后Session B中提交了一个隐式事务 ,该事务向表student中插入了一条新记录;之后Session A中的事务再根据相同的条件 studentno > 0查询表student,得到的结果集中包含Session B中的事务新插入的那条记录,这种现象也被称之为幻读 。我们把新插入的那些记录称之为幻影记录 。
SQL中的四种隔离级别
上面介绍了几种并发事务执行过程中可能遇到的一些问题,这些问题有轻重缓急之分,我们给这些问题 按照严重性来排一下序:
# 脏写 > 脏读 > 不可重复读 > 幻读
我们愿意舍弃一部分隔离性来换取一部分性能在这里就体现在:设立一些隔离级别,隔离级别越低,并发问题发生的就越多。 要解决这些并发问题,SQL标准中设立了4个隔离级别 :
- READ UNCOMMITTED :读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。
- READ COMMITTED :读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。
- REPEATABLE READ :可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍然存在。这是MySQL的默认隔离级别。
- SERIALIZABLE :串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避 免脏读、不可重复读和幻读。
MySQL查看设置事务隔离级别
# 查看隔离级别
select @@transaction_isolation
# 设置隔离级别
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL 隔离级别;
#其中,隔离级别格式:
> READ UNCOMMITTED
> READ COMMITTED
> REPEATABLE READ
> SERIALIZABLE
# 或者
SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔离级别'
#其中,隔离级别格式:
> READ-UNCOMMITTED
> READ-COMMITTED
> REPEATABLE-READ
> SERIALIZABLE
事务原理-事务日志
事务有4种特性:原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢?
-
事务的隔离性由 锁机制 实现。
-
而事务的原子性、一致性和持久性由事务的 redo 日志和 undo 日志来保证。
事务日志介绍(redo log、undo log)
- REDO LOG 称为 重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。 是存储引擎层(innodb)生成的日志,保存在磁盘上,记录的是"物理级别"上的页修改操作,比如页号、偏移量、写入了某某数据。主要为了保证数据的可靠性;
- UNDO LOG 称为 回滚日志 ,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。 有的DBA或许会认为 UNDO 是 REDO 的逆过程,其实不然。是存储引擎层(innodb)生成的日志,记录的是逻辑操作日志,比如对某一行数据进行了INSERT语句操作,那么undo log就记录一条与之相反的DELETE操作。主要用于事务的回滚(undo log记录的是每个修改操作的逆操作)和一致性非锁定读(undo log回滚行记录到某种特定的版本—MVCC,即多版本并发控制)。
redo log
为什么需要 redo log
为什么需要redo log,从两个方面出发
- 当DML语句要修改表数据时,在真正修改磁盘中的页数据时,需要将页数据读入到内存中的缓存池
buffer pool中,然后修改缓冲池中的页数据,然后缓冲池中的数据会以一定频率刷入到磁盘上(checkPoint机制);并不是执行一条DML就直接刷盘,这样效率太低。内存中页数据修改后,使用redo log记录有哪些页数据需要被修改,后续刷盘就根据redo log中的指令执行。 - 在一个事务中,redo log在commit之前就已记录好,如果事务commit后,还未进行刷盘时宕机,或是刷盘时宕机,mysql启动后也可根据redo log恢复数据,保证了事务的持久性。
REDO日志的好处、特点
- 好处:redo日志降低了刷盘频率、redo日志占用的空间非常小
- 特点:redo日志是顺序写入磁盘的 事务执行过程中,redo log不断记录
redo log记录的整体流程

第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝
第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值
第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式
第4步:定期将内存中修改的数据刷新到磁盘中
redo log的三种刷盘策略
这里的刷盘指内存中的redo log buffer刷到磁盘上的redo log file上。
注意,
redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到 文件系统缓存 (page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己来决定(比如page cache足够大了)。那么对于InnoDB来说就存在一个问题,如果交给系统来同 步,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。
InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit 提交事务 时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:
- 设置为0:表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步)
- 设置为1 (默认):表示每次事务提交时都将进行同步,刷盘操作
- 设置为2:表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自 己决定什么时候同步到磁盘文件。
默认时redo log的刷盘流程

小结: innodb_flush_log_at_trx_commit=1时
只要事务提交成功,redo log记录就一定在硬盘里,不会有任何数据丢失。
如果事务执行期间MySQL挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。可以保证ACID的D,数据绝对不会丢失,但是效率最差的。
建议使用默认值,虽然操作系统宕机的概率理论小于数据库宕机的概率,但既然使用了事务那么数据的安全相对来说更重要些。
undo log
redo log是事务持久性的保证,undo log是事务原子性的保证。在事务中更新数据的前置操作其实是要先写入一个 undo log。
undo log如何保证事务原子性:
事务需要保证原子性 ,也就是事务中的操作要么全部完成,要么什么也不做。但有时候事务执行到一半会出现一些情况,比如: 情况一:事务执行过程中可能遇到各种错误,比如服务器本身的错误 , 操作系统错误 ,甚至是突然断电导致的错误。 情况二:程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前事务的执行。 以上情况出现,我们需要把数据改回原先的样子,这个过程称之为回滚 ,这样就可以造成一个假象:这个事务看起来什么都没做,所以符合原子性要求。
每当我们要对一条记录做改动时(这里的改动可以指INSERT、DELETE、 UPDATE) ,都需要"留一手",把回滚时所需的东西记下来。比如:
- 你插入一条记录时,至少要把这条记录的主键值记下来,之后回滚的时候只需要把这个主键值对应的记录删掉就好了。(对于每个INSERT, InnoDB存储引擎会完成一个DELETE)
- 你删除了一条记录,至少要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中就好了。(对于每 个DELETE, InnoDB存储引擎会执行一个INSERT)
- 你修改了一条记录,至少要把修改这条记录前的旧值都记录下来,这样之后回滚时再把这条记录更新为旧值就好了。(对于每个UPDATE, InnoDB存储引擎会执行一个相反的UPDATE)
MySQL把这些为了回滚而记录的这些内容称之为撤销日志或者回滚日志(即undo log) 。注意,由于查询操作并不会修改任何用户记录,所以在查询操作执行时,并不需要记录相应的undo日志。
此外,undo log会产生redo log ,也就是undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。
undo log的作用
-
作用1:回滚数据
用户对undo日志可能有误解:undo用于将数据库物理地恢复到执行语句或事务之前的样子。但事实并非如此。undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。
这是因为在多用户并发系统中,可能会有数十、数百甚至数千个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。
-
作用2:MVCC
undo的另一个作用是MVCC,即在InnoDB存储引擎中MVCC的实现是通过undo来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。
undo log的生命周期
以下是undo+redo事务的简化过程
假设有2个数值,分别为A=1和B=2,然后将A修改为3,B修改为4
- start transaction;
- 记录A=1到undo log;
- update A = 3;
- 记录A=3到redo log;
- 记录 B=2到undo log;
- update B = 4;
- 记录B = 4到redo log;
- 将redo log刷新到磁盘
- commit
在1-8步骤的任意一步系统宕机,事务未提交,该事务就不会对磁盘上的数据做任何影响。
如果在8-9之间宕机,恢复之后可以选择回滚,也可以选择继续完成事务提交,因为此时redo log已经持久化。
若在9之后系统宕机,内存映射中变更的数据还来不及刷回磁盘,那么系统恢复之后,可以根据redo log把数据刷回磁盘。

版权声明
本文为[0oIronhide]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_38599840/article/details/124351345
边栏推荐
- 页面缓存问题解决方法(慎用)
- JS性能优化
- Curry realization of function continuous call calculation and accumulation
- Baidu map coordinates, Google coordinates and Tencent coordinates are mutually transformed
- Decentralized Collaborative Learning Framework for Next POI Recommendation
- LeetCode刷题|38外观数组
- The getfield () method in TP5 changes, and TP5 gets the value of a single field
- Solution to page cache problem (use with caution)
- 修改Jupyter Notebook样式
- 使用百度智能云人脸检测接口实现照片质量检测
猜你喜欢
随机推荐
freeCodeCamp----prob_calculator练习
Baidu map coordinates, Google coordinates and Tencent coordinates are mutually transformed
TP5中的getField()方法变化,tp5获取单个字段值
Ansible基本命令、角色、内置变量与tests判断
LeetCode刷题|897递增顺序搜索树
关于注解1
TP5 uses redis
页面缓存问题解决方法(慎用)
mysql中sum (if)_mysql 中sum (if())
Kids and COVID: why young immune systems are still on top
mysql密码过期的方法
Scientists say Australian plan to cull up to 10,000 wild horses doesn’t go far enough
JS正则匹配先行断言和后行断言
Your brain expands and shrinks over time — these charts show how
rdma网络介绍
el-date-picker限制选择范围,从当前时间到两个月前
Set and map
Concurrent optimization request
The getfield () method in TP5 changes, and TP5 gets the value of a single field
Each traversal usage of tp6









