文章目录
🌌事务
🌍什么是事务
事务把⼀组SQL语句打包成为⼀个整体,在这组SQL的执行过程中,要么全部成功,要么全部失败。这组SQL语句可以是⼀条也可以是多条
我们以这个转账例子为例,进行讲解
- 如果转账成功,应该有以下结果:
1. 张三的账户余额减少100,变成900,李四的账户余额增加了100,变成1100,不能出现张三的余额减少而李四的余额没有增加的情况;
2. 张三和李四在发生转账前后的总额不变,也就是说转账前张三和李四的余额总数为1000+1000=2000,转账后他们的余额总数为900+1100=2000;
3. 转账后的余额结果应当保存到存储介质中,以便以后读取;
4. 还有一点需要要注意,在转账的处理过程中张三和李四的余额不能因其他的转账事件而受到干扰;
以上这四点在事务的整个执行过程中必须要得到保证,这也就是事务的ACID特性
🌍事务的ACID特性
事务的ACID特性指的是Atomicity(原子性),Consistency(一致性),Isolation(隔离性)和Durability(持久性)
- Atomicity(原子性):一个事务中的所有操作,要么全部成功,要么全部失败,不会出现只执行了一半的情况,如果事务在执行过程中发生错误,会回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样
- Consistency(一致性):在事务开始之前和事务结束后,数据库的完成性不会被破坏,这表示写入的数据必须完全符合所有的预设规则,包括数据的精度、关联性以及关于事务执行过程中服务器崩溃后如何恢复
- Isolation(隔离性):数据库允许多个并发事务同时对数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,事务可以指定不用的隔离的级别,来权衡在不同的应用场景下数据库性能和安全
- Durability(持久性):事务处理结束后,对数据的修改将永久的写入存储介质,即便系统故障 也不会丢失
数据库服务是一个网络服务,可以同时支持多个客户端访问
🌍为什么使用事务
事务具备的ACID特性,是我们使用事务的原因,在我们日常的业务场景中有大量的需求要用事务来保证,支持事务的数据库能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题,在使用事务的过程中,要么提交,要么回滚,不用去考虑网络异常,服务器宕机等其他因素,因此我们经常接触的事务本质上是数据库对ACID模型的一个实现,是为应用层服务的
在使用数据库的过程中,对于修改只要提交成功,就可以安全的保存,只要回滚就可以回到事务之初
🌍 使用事务
🪐查看支持事务的存储引擎
🪐语法
-- 开始一个新的事务
START TRANSACTION;
-- 或者
BEGIN;
-- 提交当前事务,并对更改持久化保存
COMMIT;
-- 回滚当前事务,取消其更改
ROLLBACK;
无论提交还是回滚,事务都会关闭,开启一个事务之后,所写的SQL语句就包含在事务当中,这些SQL具有ACID特性
🪐演示回滚
注意:commit之后不能回滚,因为事务已经提交了,数据已经落盘了,不论是提交还是回滚都会关闭事务
🪐演示提交
🪐保存点
在事务执行过程中设置保存点,回滚时指定保存点可以把数据恢复到保存点的状态
🪐自动/手动提交事务
默认情况下,MySQL是自动提交事务的,也就是说我们执行的每个修改操作,比如插入、更新和删除,都会自动开启⼀个事务并在语句执行完成之后自动提交,发生异常时自动回滚。
- 查看当前事务是否自动提交
- 设置事务为自动或手动提交
# 设置事务自动提交
mysql> SET AUTOCOMMIT=1; # 方式一
mysql> SET AUTOCOMMIT=ON; # 方式二
# 设置事务手动提交
mysql> SET AUTOCOMMIT=0; # 方式一
mysql> SET AUTOCOMMIT=OFF; # 方式二
什么是DML语句?
DML 即数据操纵语言(Data Manipulation Language ),是用于对数据库中的数据进行操作的 SQL 语句,主要用于插入、更新、删除和查询数据 。它关注实际的数据处理,与用于定义和修改数据库结构的 DDL(数据定义语言)不同
注意:
- 只要使用 START TRANSACTION 或 BEGIN 开启事务,必须通过 COMMIT 提交才会持久化,或通过 ROLLBACK 回滚,才能结束事务,与是否设置 SET autocommit 无关
- 手动提交模式下,不用显示开启事务,执行修改操作后,提交或回滚事务是直接使用commit或rollback
- 已提交的事务不能回滚
本来是自动提交的,我们设置为手动后,重启MySQL就能恢复成自动提交
如果想永久的修改为手动提交,可以通过修改配置文件来设置
🌍事务的隔离性与隔离级别
🪐什么是隔离性
MySQL服务可以同时被多个客户端访问,每个客户端执行的DML语句以事务为基本单位,那么不同的客户端在对同一张表的同一条数据进行修改的时候就可能出现相互影响的情况,为了保证不同的事务执行过程中不受影响,那么事务之间就需要相互隔离,这种特性就是隔离性
🪐隔离级别
事务具有隔离性,那么如何实现事务之间的隔离? 隔离到什么程度?
如何保证数据安全的同时也能兼顾性能?
事务之间不同的隔离程度,称为事务的隔离级别;不同的隔离级别在性能和安全方面做了取舍,有的隔离级别注重并发性,有的注重安全性,有的则是并发和安全适中;在MySQL的InnoDB引擎中事务的隔离级别有四种
- READ UNCOMMITTED , 读未提交
- READ COMMITTED,读已提交
- REPEATABLE READ,可重复读(默认)
- SERIALIZABLE , 串行化
🪐举例说明四种隔离级别
场景:我们用新老博客举一个现实生活中的列子 – 博客写完后我可能会根据之后学习到的知识对之前的博客进行一点修改
1.READ UNCOMMITTED 读未提交
比如我有一篇没写完的博客放在草稿箱中,在这时一个同学来我的电脑上想提前看看博客,将草稿箱的博客拷走了一份
我回来后继续写博客,发布完整版的博客,同学发现他拿到的博客与我发布的内容不符,也就是说他拿走了我还没提交的事务
对应到事务中,事务A对数据进行修改,事务B访问了事务A还没有提交的数据,这个情况就叫做"脏读",事务B访问了事务A rollback的数据2.READ COMMITTED 读已提交
比如我把新的博客发布CSDN平台,这个发布的过程就相当于提交
同学们正在访问我已经提交的博客时,我对博客进行修改操作,然后发布修改后的博客
假设同学A在看"事务"这篇博客时,我对"事务"这篇博客修改并发布(提交),然后同学A一刷新,发现第一次读取的内容与刷新后的读取的内容不一致
对应到事务中,事务A第一次查询了某条记录,此时事务B对这条记录进行了修改并提交,当事务A再次查询这条记录时,发现与第一次的查询结果不一致,这个现象就叫做"不可重复读"3.REPEATABLE READ 可重复读(默认)
比如同学A正在看"事务"这篇博客时,给我发了个消息,说朝新啊,你先不要修改这篇博客,我再看一看,这就相当于给这篇博客上了一把锁
但是对于其他博客的增删,都是可以的,那么同学A第一次访问博客列表时查出来了32篇博客,当我新增一篇博客时,同学A再查询博客列表,发现博客数据变为33篇了
对应到事务中,事务A第一次查询某个结果集,那么以相同的查询得到的结果集与第一次查询不同,这个现象就叫做"幻读",两次相同的查询得到的结果集不同
InnoDB存储引擎中,使用了next-key 锁,锁住了目标行和之前的间隙,解决了部分的幻读问题
- 4.SERIALIZABLE 串行化
解决了所有的数据安全问题,所有的事务都是一个挨着一个执行的,一个事务必须要等到上一个事务执行完才能执行
注意区分不可重复读和幻读
不可重复度,指的是某一行数据 幻读指的是,结果集
🪐不同隔离级别的性能与安全
🪐查看和设置隔离级别
事务的隔离级别分为全局作用域和会话作用域,查看不同作用域事务的隔离级别可以试用一下指令
设置事务的隔离级别和访问模式,可以使用以下语法:
-- 通过GLOBAL | SESSION 分别指定不同的作用域和事务隔离级别
set [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL level | access_mode;
-- 其中level 是四种隔离级别
-- 其中access_mode 是两种访问模式
# 隔离级别
level: {
REPEATABLE READ # 可重复读
| READ COMMITTED # 读已提交
| READ UNCOMMITTED # 读未提交
| SERIALIZABLE # 串行化
}
# 访问模式
access_mode: {
READ WRITE # 表示事务可以对数据进行读写
| READ ONLY # 表示事务是只读,不能对数据进行读写
}
# 方式一
SET GLOBAL transaction_isolation = 'SERIALIZABLE';
# 注意使用SET语法时 有空格要用"-"代替
SET SESSION transaction_isolation = 'REPEATABLE-READ';
# 方式二
SET @@GLOBAL.transaction_isolation='SERIALIZABLE';
# 注意使用SET语法时 有空格要用"-"代替
SET @@SESSION.transaction_isolation='REPEATABLE-READ';
演示:
🌍通过两个会话观察不同隔离级别所存在的问题
🪐READ-UNCOMMITTED 读未提交与脏读
出现在事务的 READ UNCOMMITTED 隔离级别下,由于在读取数据时不做任何限制,所以并发性能很高,但是会出现大量的数据安全问题,比如在事务A中执行了一条 INSERT 语句,在没有执行 COMMIT 的情况下,会在事务B中被读取到,此时如果事务A执行回滚操作,那么事务B中读取到的事务A写入的数据将没有意义,我们把这个现象叫做“脏读”
OK,展示! !
首先,在会话A中开启事务A
在会话B中进行查询
在会话A中进行回滚操作
会话A进行回滚后,到会话B中再次进行查询,与第一次查询结果不一致
由于READ UNCOMMITTED 读未提交会出现"脏读"现象,在正常的业务中会出现这种问题产生非常严重的后果,所以正常情况下应该避免使用READ UNCOMMITTED 读未提交这种隔离级别
🪐READ-COMMITTED 读已提交与不可重复读
为解决脏读问题,可以把事务的隔离级别设置为 READ COMMITTED ,这时事务只能读到了其他事务提交之后的数据,但会出现不可重读的问题,比如事务A先对某条数据进行了查询,之后事务B对这条数据进行修改,并且提交(COMMIT)事务,事务A再对这条数据进行查询时,得到了事务B修改之后的结果,这导致了事务A在同一事务中以相同的条件查询得到了不同的值(不同的一行数据),这个现象叫做"不可重复读"
OK,展示! !
首先,在会话B中开启事务B
在会话A中修改水母的余额
在会话B中再次查询水母的余额,发现与第一次查询的结果不一致
🪐REPEATABLE-READ 可重复读与幻读
为了解决不可重复读的问题,可以把事务的隔离级别设置为 REPEATABLE READ ,这时同一个事务中读取的数据在任何时候都是相同的结果,但还会出现一个问题,**事务A查询了一个区间的记录得到结果集A,**事务B向这个区间的间隙中写入了一条记录并提交,事务A再查询这个区间的结果集时会查到事务B新写入的记录得到结果集B,两次查询的结果集不一致,这个现象就叫做“幻读”
MySQL的InnoDB存储引擎使用了Next-Key锁解决了大部分幻读问题
注意:由于REPEATABLE READ 隔离级别默认使用Next-Key锁,为了重现幻读问题,我们把隔离级别回退到更新时只加了排他锁的 READ COMMITTED
OK,展示! !
首先在会话B中开启事务,并更新水母的余额
在会话A中的数据间隙之间写入一条数据
并在会话A中提交事务
在会话B中再次查询,发现比上一的查询多出了其他事务写入的数据,多出了幻像行
🪐SERIALIZABLE 串行化
进一步提升事务的隔离级别到 SERIALIZABLE ,此时所有事务串行执行,可以解决所有并发中的安全问题