文章目录
- 前言
- 1.什么是事务?
- 2.事务的ACID特性
- 3.为什么要使用事务?
- 4.如何使用事务
- 4.1.查看支持事务的 存储引擎
- 4.2.语法
- 4.3.开启一个事务,执行修改后回滚
- 4.4.开启一个事务,执行修改后提交
- 4.5.保存点
- 4.6.自动/手动提交事务
- 5.事务的隔离性和隔离级别
- 5.1.什么是隔离性
- 5.2.隔离级别
- 5.3.查看和设置隔离级别
- 6.不同隔离级别存在的问题
- 6.1.READ UNCOMMITTED
- 6.1.1. 出现的问题
- 6.1.2. 代码实现
- 6.2.READ COMMITTED
- 6.2.1.出现的问题
- 6.2.2. 代码的实现
- 6.3.REPEATABLE READ
- 6.3.1. 出现的问题
- 6.3.2. 代码的实现
- 6.4.SERIALIZABLE
- 6.5.不同隔离级别的性能与安全
前言
前面博客都是在一个服务端中进行的,今天我们学习在两个服务器段进行的,看看会出现哪些意想不到的问题
1.什么是事务?
事务是一组SQL语句打包成为一个整体,在这组SQL的执行过程中,要么全部成功,要么全部失败。这组SQL语句可以是一条也可以是多条。先看一个转账的例子,如图:
为了完成上述的操作,我们来建立账户表
create table account(
id bigint primary key auto_increment,
name varchar(20) not null,
balance decimal(10,2) not null
);
insert into account(name,balance) values ("张三",1000);
insert into account(name,balance) values ("李四",1000);
update account set balance = balance - 100 where name = '张三';
update account set balance = balance + 100 where name = '李四';
- 如果转账成功,应该有以下结果:
- 张三的账户余额减少** 100**,变成900,李四的账户余额增加了100,变成1100,不能出现张三的余额减少而李四的余额没有增加的情况;
- 张三和李四在发生转账前后的总额不变,也就说转账前张三和李四的余额总数为1000+1000=2000,转账后他们的余额总数为900+1100=2000;
- 转账后的余额结果应当保存到存储介质中,以便以后读取;
- 以后一点需要注意,在转账的处理过程中张三和李四的余额不能因其他的转账事件而受到干扰。
以上这四点在事务的整个执行过程中必须要得到保证,这也就是事务的ACID特性
根据下面的图片,我们发现结果是符合上面的描述。
2.事务的ACID特性
事务的ACID特性指的是Atomicity(原子性),Consistency(一致性),Isolation(隔离性) 和Durability(持久性)
- Atomicity(原子性): 一个事务中所有操作,要么全部成功,要么全部失败,不会出现只执行了一半的情况,如果事务在执行过程中发生错误,会回滚(Rollback)到事务开始前的状态,就想这个事务从来没有执行过一样。
- Consistency(一致性): 在事务开始之前和事务结束以后,数据库的完整性不会被破坏。这表示写入的数据必须完全符合所有的预设规则,包括数据的精度、关联性以及关于事务执行过程中服务器崩溃后如何恢复。
- Isolation(隔离性): 数据库允许多个并发事务同时对数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一样。事务可以指定不同的隔离级别,以权衡在不同的应用场景下数据库性能和安全
- Durability(持久性): 事务处理结束后,对数据的修改将永久的写入存储介质中,即使系统故障也不会丢失。
3.为什么要使用事务?
事务具备的ACID特性,是我们使用事务的原因,在我们日常的业务场景中有大量的需求要用事务来保证。支持事务的数据库能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题,在使用事务过程中,要么提交,要么回滚,不用去考虑网络异常,服务器宕机等其他因素,因此我们经常接触的事务本质上是数据库对 ACID 模型的⼀个实现,是为应用层服务的
4.如何使用事务
4.1.查看支持事务的 存储引擎
要使用事务那么数据库就要支持事务,在MySQL中支持事务的存储引擎是InnoDB,可以通过
show engines;
4.2.语法
通过以下语句可以完成对事务的控制
- #开启一个事务
- start transaction;
- #或
- begin;
- #提交事务,并对更改持久化保存
- commit;
- #回滚当前事务,取消其更改
- rollback;
- start transaction 或 begin 开始一个新事务
- commit 提交当前事务,并对更改持久化保存
- rollback 回滚当前事务,取消其更改
- 无论提交还是回滚,事务都会关闭
4.3.开启一个事务,执行修改后回滚
示例:张三的余额减少100,李四的余额增加100,最后回滚到初始状态
第一步:开启事务
start transaction;
第二步:查看account表
select * from account;
第三步:将张三的余额减少100,李四的余额增加100
update account set balance = balance - 100 where name = '张三';
update account set balance = balance + 100 where name = '李四';
第四步:查看结果集
select * from account;
第五步:回滚到最开始的位置
rollback;
最后:查看结果集,是否跟最开始的一样
select * from account;
结束。
4.4.开启一个事务,执行修改后提交
示例:张三的余额减少100,李四的余额增加100,最后提交。
其实跟上面的流程都一样,我只把代码写一下,大家敲一下,看看结果就知道了。
start transaction;
select * from account;
update account set balance = balance - 100 where name = '张三';
update account set balance = balance + 100 where name = '李四';
select * from account;
commit;
select * from account;
4.5.保存点
在事务执行的过程中设置保存点,回滚时指定保存点可以把数据恢复到保存点的状态
语法:
savepoint 保存点的名字;
示例:分别进行下面三个步骤,分别设置一个保存点,然后进行回滚,看看结果集情况
- 张三的余额减少100,李四的余额增加100
- 张三的余额减少100,李四的余额增加100
- 插入一条消息,王五,余额3000
(先说好哦,前面如果commit那些操作过的话,张三的余额是900,李四的余额是1100,记得要把张三的余额变为1000,李四的余额变为1000,不然张三同学不高兴的)
start transaction;
select * from account;
-- 第一步:张三的余额减少100,李四的余额增加100
update account set balance = balance - 100 where name = '张三';
update account set balance = balance + 100 where name = '李四';
select * from account;
-- 设置保存点1
savepoint sp1;
-- 第二步:张三的余额减少100,李四的余额增加100
update account set balance = balance - 100 where name = '张三';
update account set balance = balance + 100 where name = '李四';
select * from account;
-- 设置保存点2
savepoint sp2;
-- 第3步:插入一条消息,王五,余额3000
insert into account values(null,'王五',3000);
select * from account;
-- 设置保存点3
savepoint sp3;
-- 回滚到保存点2
rollback to sp2;
-- 查看结果集
select * from account;
-- 回滚最初的时候
rollback;
如果在设置完保存点3的时候,如果直接回滚到了sp1这个位置的话,就无法回滚到sp2或sp3这个位置了。具体的内容,大家操作一下,必要的时候,可以对上面的代码进行修改。
4.6.自动/手动提交事务
默认情况下,MySQL是自动提交事务的,也就是说我们执行的每个修改操作,比如插入、更新和删除,都是自动开启一个事务并在语句执行完成之后自动提交,发生异常时自动回滚。
查看当前事务是否自动提交
show variables like 'autocommit';
可以使用一下的操作来禁止或者开启事务是否自动提交
自动提交
set autocommit = 1;
或者
set autocommit = on;
手动提交
set autocommit = 0;
或者
set autocommit = off;
注意:
- 只要使用 START TRANSACTION 或 BEGIN 开启事务,必须要通过 COMMIT 提交才会持久化,与是否设置 SET autocommit 无关。
- 手动提交模式下,不用显示开启事务,执行修改操作后,提交或回滚事务时直接使用commit或 rollback
- 已提交的事务不能回滚
5.事务的隔离性和隔离级别
5.1.什么是隔离性
MySQL服务可以同时被多个客户端访问,每个客户端执行的DML语句以事务为基本单位,那么不同的客户端在对同一张表中的同一条数据进行修改的时候就可能出现相互影响的情况,为了保证不同的事务之间在执行的过程中部首影响,那么事务之间就需要相互隔离,这种特性就是隔离性
5.2.隔离级别
事务具有隔离性,那么如何实现事务之间的隔离?隔离到什么程度?如何保证数据安全的同时也要兼顾性能?这都是要考虑的问题。
事务之间不同程度的隔离,称为事务的隔离级别,不同的隔离级别在性能和安全方面做了取舍,有的隔离级别注重并发性,有的注重安全性,有的则是并发和安全适中,在MySQL的InnoDB引擎中事务的隔离级别有四种,分别是:
- READ UNCOMMITTED,读未提交
- READ COMMITTER,读已提交
- REPEATABLE READ,可重复读(默认)
- SERIALIZABLE,串行化
*
5.3.查看和设置隔离级别
- 事务的隔离级别分为全局作用域和会话作用域,查看不同作用域事务的隔离级别,可以使用以下的方式:
-- 全局变量
select @@GLOBAL.transaction_isolation;
-- 会话作用域
select @@SESSION.transaction_isolation;
- 设置事务的隔离级别和访问模式,可以使用以下语法:
select [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level|access_mode;
隔离级别:
level:{
REPEATABLE READ # 可重复度
|READ COMMITTED #读已提交
|READ UNCOMMITTED # 读未提交
|SERIALIZABLE # 串行化
}
访问模式:
access_mode:{
READ WRITE # 表示事务可以对数据进行读写
| READ ONLY # 表示事务是只读,不能对数据进行读写
}
示例:
1.设置全局事务隔离级别为串行化,后续所有事务生效,不影响当前事务
set global transaction isolation level serializable;
2. 设置全局事务隔离级别为串行化,后续所有事务生效,不影响当前事务
set session transaction isolation level serializable;
3 如果不指定任何作用域,设置只针对下一个事务,随后的事务恢复之前的隔离级别
set global transaction_isolation = 'repeatable-read';
或者
set session transaction_isolation = 'seriablizable';
6.不同隔离级别存在的问题
接下的过程都需要两个MySQL的客户端,大家提前准备两个客户端,分别是客户端A和客户端B
6.1.READ UNCOMMITTED
6.1.1. 出现的问题
出现在事务的 READ UNCOMMITTED 隔离级别下,由于在读取数据时不做任何限制,所以并发性能很高,但是会出现量的数据安全问题,比如在事务A中执行了一条 INSERT 语句,在没有执行COMMIT 的情况下,会在事务B中被读取到,此时如果事务A执行回滚操作,那么事务B中读取到事务A写入的数据将没有意义,我们把这个现象叫做 “脏读”
6.1.2. 代码实现
首先先把隔离级别设置为 READ UNCOMMITTED
set global transaction isolation level read uncommitted;
考虑到 Markdown版本中表格里是不能添加代码段的,因此只能以图片的形式进行展示
6.2.READ COMMITTED
6.2.1.出现的问题
为了解决脏读的问题,可以把事务的隔离级别设置为READ COMMITTED,这时事务只能读到其他事务提交之后的数据,但会出现不可重复读的问题,比如事务A先对某条数据进行了查询,之后事务B对这条数据进行了修改,并且提交(COMMIT)事务,事务A再对这条数据进行查询时,得到了事务B修改之后的数据,这导致了事务A在同一个事务中以相同的条件查询得到了不同的值,这个现象叫做 “不可重复读”。
6.2.2. 代码的实现
首先把隔离级别设置为 READ COMMITTED
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
6.3.REPEATABLE READ
6.3.1. 出现的问题
为了解决不可重复读这个问题,可以把事务的隔离级别设置为REPEATABLE READ,这时同一个事务中读取的数据在任何时候都是相同的结果,但还会出现一个问题,事务A查询了一个区间的记录得到结果集A,事务B向这个区间的间隙中写入了一条记录并提交,事务A在查询这个区间的结果集时会查到事务B新写入的记录得到结果集B,再次查询的结果集不一致,这个现象就是 “幻读”
MySQL的InnoDB存储引擎使用了Next-key解决了大部分幻读问题
6.3.2. 代码的实现
由于 REPEATABLE READ 隔离级别默认使用了 Next-Key 锁,为了重现幻读问量,我们把隔离级回退到更新时只加了排他锁的 READ COMMITTED
首先把隔离级别设置为 READ COMMITTED
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
6.4.SERIALIZABLE
进⼀步提升事务的隔离级别到 SERIALIZABLE ,此时所有事务串行执行,可以解决所有并发中的安全问题
6.5.不同隔离级别的性能与安全
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 存在 | 存在 | 存在 |
READ COMMITTED | 解决 | 存在 | 存在 |
REPEATABLE READ | 解决 | 解决 | 存在 |
SERIALIZABLE | 解决 | 解决 | 解决 |
完