事务的四种要素(ACID)
- 原子性(Atomicity):事务开始后的所有操作,要么全部做完,要么全部__不做。
- 一致性(Consistency):事务开始前和结束后,数据库的完整性拘束没有被破坏。比如A向B赚钱,不可能A扣了钱,B没收到。
- 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同事务之间彼此没有相互干扰。比如A正在向一张银行卡取钱,在A操作完之前,B不能向该银行卡转账。
- 持久性(Durability):事务完成后,事务对数据库的所有更新都将保存到数据库,不能回滚。
事务的并发问题
当多个事务同时在数据库上执行时,就有可能出现以下这些事务的并发问题。
- 脏读:事务A读取了事务B更新的数据,然后事务B进行了回滚操作,那么A读取到的数据就是脏数据。
- 不可重复读:事务A多次读取同一数据,事务B在多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
- 幻读:指一个事务在前后两次查询一个范围的时候,后一次查询看到前一次查询没有看到的列。
幻读专指新插入的数据行,不可重复读侧重于对原有数据的修改。
事务的隔离级别
为了解决上面的事务的并发问题,于是就有了以下隔离级别的概念。
事务的隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read uncommitted) | Y | Y | Y |
读已提交(read committed) | N | Y | Y |
可重复读(repeatable read) | N | N | Y |
串行化(serializable) | N | N | N |
这张表是事务的4种隔离级别以及在这种隔离界别下会出现的并发问题。
MySQL的默认隔离级别是可重复读。
查看当前session隔离级别命令:select @@tx_isolation;
为了方便演示,使用的测试表如下:
create table t
(
id int not null
primary key,
c int null,
d int null
);
create index c
on t (c);
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
读未提交(read uncommited)
设置当前事务为读未提交命令:
set session transaction isolation level read uncommitted;
假设:
sessionA | sessionB | |
---|---|---|
T1 | begin; | |
T2 | select * from t;result:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) | |
T3 | begin; | |
T4 | update t set c = c + 5 where id = 5; | |
T5 | select * from t;result:(0,0,0),(5,10,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) | |
T6 | rollback; | |
T7 | select * from t;result:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) |
- 读未提交表现:T4时刻sessionB更新了id为5的数据,并且没有提交事务,但是sessionA在T5时刻读取到sessionB的更新。
- 脏读表现:T6时刻sessionB进行了回滚,因此sessionA在T5时刻读取到的是脏数据。
读已提交(read committed)
设置当前事务为读已提交命令:
set session transaction isolation level read committed;
例如:
sessionA | sessionB | |
---|---|---|
T1 | begin; | |
T2 | select * from t;result:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) | |
T3 | begin; | |
T4 | update t set c = c + 5 where id = 5; | |
T5 | select * from t;result:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) | |
T6 | commit; | |
T7 | select * from t;result:(0,0,0),(5,10,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) |
- 读已提交表现:sessionB在T4时刻更新了数据,sessionA在T5时刻并没有读取到更新数据,直到T6时刻sessionB提交了之后sessionA才能读取到更新数据。
- 不可重复读表现:T6时刻sessionB提交了事务,导致sessionA在事务中T5和T7时刻"select"读取到的数据不一致,产生了不可重复读问题。
可重复读(repeatable read)
设置当前事务为可重复读命令:
set session transaction isolation level repeatable read;
例如:
sessionA | sessionB | |
---|---|---|
T1 | begin; | |
T2 | select * from t;result:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) | |
T3 | begin; | |
T4 | update t set c = c + 5 where id = 5; | |
T5 | select * from t;result:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) | |
T6 | commit; | |
T7 | select * from t;result:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) | |
T8 | commit; | |
T9 | select * from t;result:(0,0,0),(5,10,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) |
- 可重复读表现:sessionA在T2,T5和T7时刻读取到的数据一致,也就是说在当前事务中select读取的数据不会引其他事务的提交而变化。
串行化(serializable)
设置当前事务为串行化命令:
set session transaction isolation level serializable;
示例:
sessionA | sessionB | |
---|---|---|
T1 | begin; | |
T2 | select * from t for update;result:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25) | |
T3 | insert into t (id, c, d) values (6, 10, 6);(blocked) |
- 串行化表现:sessionB插入新数据时直接被锁住直到sessionA事务提交。(串行化的锁级别是表级锁,读写都会锁住整个表)。
可重复读怎么解决不可重复读问题
读已提交隔离级别和可重复读隔离级别下事务的视图生成是不同的。
- 读已提交(read committed):每一个语句执行前都会重新计算出一个视图。
- 可重复读(repeatable read):在事务开始时创建一个一致性视图,之后事务里的其他查询都共用这个一致性视图。