数据库事务之隔离级别
数据库事务四大属性(ACID特性):
原子性(atomicity) 一个事务是不可分割的一部分,要么都做,要么都不做;
一致性(consistency)一个事务必须是从一个一致性状态到另一个一致性状态;
隔离性(isolation) 一个事务的内部操作不能被另一个事务干扰;
持久性(durability) 一个事务一旦提交,它对数据库的更改是永久性的(我的理解应该指落到了磁盘上而不是还在内存) 。
隔离性的级别
隔离性的几个级别:0 未提交读(read uncommitted) : 未提交读会产生脏读,即一个事务可以读取另一个未提交的事务的数据(更改);
1 提交读(read committed):会产生不可重复读和幻读,即一个事务中两次读,第二次读到别的事务的数据(更改,提交后的);
2 可重复读(repeatable read):避免脏读,不可重复读,允许幻像读;
3 串行化读(serializable),事务只能一个一个执行,避免了脏读、不可重复读、幻读;
实验数据的环境:
windows10,mysql8.0.13,datagrip2019.2
脏读示例
开启三个连接:
A连接使用read uncommitted;
#脏读演示 A
# step0
set session transaction isolation level read uncommitted;
# step1.1 A读数据 balance = 0;
select * from bank_account where id = 1;
# step2.1 A开启事务(不开亦可);
start transaction;
# step5.1 A读数据 balance = 1;
select * from bank_account where id = 1;
# step6.1 A提交(同step2.1);
commit ;
B连接使用read uncommitted;
# 脏读演示 B
# step0
set session transaction isolation level read uncommitted;
# step0 设置不自动提交事务(这里是session级别的)
set autocommit=0;
#step3 B开启事务
start transaction ;
#step4 B更新数据更改 balance = 1
update bank_account set balance = 1 where id = 1;
#step7 B提交事务
commit ;
C连接使用repeatable read(mysql默认级别);
#脏读演示 C
# step0
set session transaction isolation level REPEATABLE READ;
# step1.2 C读数据 balance = 0;
select * from bank_account where id = 1;
# step2.2 C开启事务(不开亦可);
#start transaction;
# step5.2 C读数据 balance = 0;
select * from bank_account where id = 1;
# step6.2 C提交(同step2.2);
commit ;
首先设置三个连接的session隔离级别;可以使用 以下语句查看是否设置正确:
show session variables like '%isolation%';
衍生一个题外话:MySQL隔离级别分为session和global两种
设置global的方式只需要把session替换成global即可
按照上述语句依次运行即可(语句上方为说明)
按照顺序从上往下依次执行
总结:
可以发现read uncommitted 级别,可以随意读取别人事务中更改的数据(别人未提交事务),这个级别相当于根本没有事务的四大属性之—>隔离性(isolation)了,别人只是声明了要更改数据,你便相信了,别人要是反悔了呢(rollback);
必须要两个连接都是设置的可以发生脏读的级别才可以看到数据,演示C是repeatable 便看不到事务B中的数据。
不可重复读示例
#不可重复读演示 A
# step0
set session transaction isolation level read committed;
# step1.1 A读数据 balance = 0;
select * from bank_account where id = 1;
# step2.1 A开启事务;
start transaction;
# step3.1 A读数据 balance = 0;
select * from bank_account where id = 1;
# todo something
# step 6.1 A读数据 balance = 0 这里说明已经避免了脏读;
select * from bank_account where id = 1;
# step 8.1 A读数据 balance = 1 这里出现了不可重复读;
select * from bank_account where id = 1;
# step9.1 A提交(同step2.1);
commit ;
# step 10.1 A读数据 balance = 1 ;
select * from bank_account where id = 1;
# 不可重复读演示 B
# step0
set session transaction isolation level read committed;
#step4 B开启事务
start transaction ;
#step5 B更新数据
update bank_account set balance = 1 where id = 1;
#step7 B提交事务
commit ;
#不可重复读演示 C
# step0
set session transaction isolation level REPEATABLE READ;
# step1.2 C读数据 balance = 0;
select * from bank_account where id = 1;
# step2.2 C开启事务;
# start transaction;
# step3.2 C读数据 balance = 0;
select * from bank_account where id = 1;
# step6.2 C读数据 balance = 0 这里说明已经避免了脏读;
select * from bank_account where id = 1;
# step8.2 C读数据 balance = 0 这里避免了不可重复读 ;
select * from bank_account where id = 1;
# step9.2 C提交(同step2.2);
commit ;
#step 10.2 C读数据 balance = 1
select * from bank_account where id = 1;
按照上述语句依次运行即可(语句上方为说明)
按照顺序从上往下依次执行
总结:
可以发现read commited 级别,可以在自己的事务中读取别人事务已经提交的数据(自己事务未提交),会出现两次读取数据记录不一样的结果。
幻读
这里我觉得有些许争议:关于不可重复读和幻读的
解释一(百度百科):隔离级别
不可重复读:一事务对数据进行了更新或删除操作,另一事务两次查询的数据不一致幻读:一事务对数据进行了新增操作,另一事务两次查询的数据不一致
解释二(CSDN):littlexiaoshuishui对于这篇文章的回答
不知道是谁发明了不可重复读和幻读问题,这本质都是一样的东西啊,就是一个事务内两次读取同一些数据,结果不一样。这就是统一的幻读问题啊。行级锁可以解决更新和删除导致的幻读,间隙锁可以解决插入导致的幻读。为啥要分开呢,导致很多人看的时候云里雾里,而且很多人也是跟着人云亦云。
总结:
幻读和不可重复读非常类似,这里留给读者自己思考...
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 会 | 会 | 会 |
读已提交 | 不会 | 会 | 会 |
可重复读 | 不会 | 不会 | 会 |
串行 | 不会 | 不会 | 不会 |
演示数据脚本:
create table bank_account
(
id int auto_increment,
consumer varchar(20) not null,
balance int default 0 not null,
constraint bank_account_consumer_uindex
unique (consumer),
constraint bank_account_id_uindex
unique (id)
);
alter table bank_account
add primary key (id);
到此,数据库隔离级别介绍完毕