大家有没有想过一个问题,我们为什么需要事务?事务解决了怎样的问题?事务又会带来什么新的问题?本人也是带着疑惑一步步去深入学习,特此总结一下。
一、基础概念
1.1 什么是事务?
事务(Transaction) 是访问和更新数据库的程序执行单元,事务中可能包含一个或多个SQL语句。事务最大的特点就是,要么SQL语句都执行,要么都不执行。
1.2 事务条件
事务必须总是满足以下的四个条件:
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
以上四个条件简称ACID,下文将对事务条件逐一进行讲解。
1.2.1 原子性
用一个最简单的案例来说:
上面这个转账操作,可以分为三条sql语句:
- 查询张三余额是否>100元,若满足则执行后续SQL,否则返回。
- 张三余额减去100元
- 李四余额加100元
在这个案例当中,我们应该确保上面的转账操作要么都成功进行,要么都失败,才能确保交易的合法性,即就是事务不会结束在书屋中间某个环节,如果在事务的执行过程中发生错误,则会回滚到事务开始的状态。即满足了事务的 原子性条件。
1.2.2 一致性
事物的一致性有点难理解,至少对于我是这样的,而在我搜集很多资料总结的时候,我看到了这么一句话:事务一致性是:应用系统从一个正确的状态到另一个正确的状态,我瞬间恍然大悟了。一致性关注数据的可见性,中间状态的数据对外部不可见,只有最初状态和最终状态的数据对外可见。
1.2.3 隔离性
多个用户访问数据库时,各自的事务具有隔离性,每个事务都有自己的数据空间,不会相互干扰。
1.2.4 持久性
执行事务提交后,数据会永久性保留在硬盘中。
二、事务隔离级别
mysql 8.0+可以查看当前隔离级别:
select @@transaction_isolation;
以前的老版本查看隔离级别是:
select @@tx_isolation;
mysql事务隔离级别分为以下四种:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 (read-uncommitted) | 是 | 是 | 是 |
读已提交 (read-committed) | 否 | 是 | 是 |
可重复读 (repeatable-read) | 否 | 否 | 是 |
串行化 (serializable) | 否 | 否 | 否 |
名词解释:
脏读:事务A和事务B同时执行,此时事务A还没有提交修改的数据,但是事务B读取到事务A改变后的数据,这就是脏读。
不可重复读:事务A读取一条记录,此时事务B对该数据 进行了操作(改变了原数据)并提交事务B,此时事务A再次查询数据与第一次读取的不一样,这就是不可重复读(两次读取的不一样啊,就是说重复读的话可能会前后矛盾,所以可以理解成不可重复读。)。
幻读:事务A、B同时开启,若数据库此时有两条数据(id分别是1,2),事务B此时插入了一条数据(id为3),同时提交事务B,此时事务A再插入id为3的数据的时候,会提示插入失败,但是查询数据依然只能查到两条数据,这就是幻读。
不可重复读重点在于update和delete,而幻读的重点在于insert
在进行介绍之前,我们首先创建一个测试表。
CREATE DATABASE study;
use stydy;
CREATE TABLE person(
name varchar(20),
country varchar(20),
salary decimal,
id int (11) primary key auto_increment
);
insert into person(id,name,salary,country) values(1,"小王",1000,"中国");
2.1 读未提交
读未提交可能会出现脏读、不可重复读、幻读。
(a)脏读
脏读:若同时有多个事务A,B开启,事务B改变了数据但是没有提交,但此时事务A中可以读到事务B中未提交的数据。若此时B事务回滚,则出现了脏读。
我们先来开启两个事务:
事务A | 事务B |
---|---|
set session transaction isolation level read uncommitted; | set session transaction isolation level read uncommitted; |
start transaction; | start transaction; |
select *from person; | |
update person set salary=2000 where name=“小王”; | |
select *from person; | |
commit; | commit; |
可以看到,事务B未提交,但是已经影响了事务A,所以这就是脏读。
2.2 读已提交
读已提交不会出现脏读,但是还是会带来不可重复读和幻读。
(a)不可重复读:
若同时有多个事务A,B开启,事务A先读取一次数据,事务B改变了数据并提交事务B,此时事务A再读取数据就是被事务B更新过的值,与第一次读取的结果不一致,这就是不可重复读。
我们先来开启两个事务:
事务A | 事务B |
---|---|
set session transaction isolation level read committed; | set session transaction isolation level read committed; |
start transaction; | start transaction; |
select *from person; | |
update person set salary=2000 where name=“小王”; | |
select *from person; 可以看到已经解决了脏读 | |
commit; | |
select *from person; | |
commit; |
可以看到解决了脏读,但是还是会出现读取结果前后不一致的情况。即就是不可重复读。
2.3 可重复读
(a)幻读
两个事务同时开启的时候,若一方对数据库insert数据,则会出现幻读!
我们先来开启两个事务:
事务A | 事务B |
---|---|
set session transaction isolation level repeatable-read; | set session transaction isolation level repeatable-read; |
start transaction; | start transaction; |
select *from person; | |
update person set salary=2000 where name=“小王”; | |
select *from person; 可以看到已经解决了脏读 | |
commit; | |
select *from person; 可以看到已经解决了不可重复读 | |
start transaction; | |
insert person(id,name,salary,country) values(0,“小张”,5000,“加拿大”); | |
commit; | |
insert person(id,name,salary,country) values(0,“小张”,5000,“加拿大”); | |
select * from person; 出现了幻读,明明只有一条数据还是插入失败? | |
commit; |
2.4 串行化隔离级别
事务隔离级别设置为串行化时,如果同时开启多个事务,为保证其他事务不会幻读到这个数据,insert数据时会“卡住”,直到其他事务提交。insert才可以执行完成添加操作,这样即就解决了幻读问题!
总结:
以上例子说明了事务隔离级别不同可能会带来不同的结果,另外事务隔离级别为串行化时,读写数据都会锁住整张表,隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。因为这个并不常用。