0.事务的基本概念
一般说到事务,一定会有ACID四个特点:
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
1.事务的四个特性
1.1.原子性(Atomicity)
事务操作要么同时成功,要么同时失败
如上图事务(A转账给B 200元)包含两个过程:
A:800 - 200 = 600(元)
B:200 + 200 = 400(元)
这两个过程在一个事务中,必须同时成功或同时失败,不能只发生其中一个动作
1.2.一致性(Consistency)
事务前后数据完整性必须一致(数据仍然正确有效)
步骤同上,一致性要求这两个步骤符合逻辑,数据正确
1.3.隔离性(Isolation)
多个用户并发访问数据库时,数据库应为每一个用户单独开启事务
多个并发事务之间要相互隔离,不能被其他事务的操作干扰
若这两个事务同时进行,那么就会出现问题:
- 脏读:一个事务读取了另一个事务尚未提交的数据
- 不可重复读:一个事务涉及多次读取,在此过程中有另一事务改变了其中数据
- 幻读(虚读):一个事务涉及多次读取,在此过程中有另一事务插入新的数据
这三个概念将在下面讲解
1.4.持久性(Durability)
事务一旦提交,对数据库的操作就是永久的
2.为什么需要隔离性
如果事务之间不是相互隔离,可能会出现以下的问题:
2.1.脏读
一个事务读取了另一事务尚未提交的数据
临时处理而尚未提交的数据,我们认为它是“脏”的
假设A与C同时向B转账:
事务一:A向B转账200元
事务二:C向B转账100元
正确数据 | “脏读”导致的数据 | |
---|---|---|
A | 800 - 200 = 600(元) | 800 - 200 = 600(元) |
B | 200 + 100 + 200 = 500(元) | 200 + 100 = 300(元) |
C | 1000 - 100 = 900(元) | 1000 - 100 = 900(元) |
2.2.不可重复读(针对某一条记录:update和delete)
一个事务涉及多次读取,在此过程中有另一事务改变了其中数据
仍然假设A与C同时向B转账
事务一:B多次查询自己的余额(查询一条记录)
事务二:A向B转账200元
事务三:C向B转账100元
则可能会出现以下的执行状况:
事务一(B查询余额) | 事务二(A向B转账200元) | 事务三(C向B转账100元) | |
---|---|---|---|
1 | 开启事务一 | ||
2 | 开启事务二 | ||
3 | 开启事务三 | ||
4 | 查询余额:200元 | ||
5 | A向B转账200元 | ||
6 | 提交事务 | ||
7 | 查询余额:400元 | ||
8 | C向B转账100元 | ||
9 | 提交事务 | ||
10 | 查询余额:500元 |
事务一在执行的过程中,因为事务二和事务三的干扰,多次查询出的余额都不一致,这就是不可重复读
2.3.幻读(针对多条记录:insert)
一个事务涉及多次读取,在此过程中有另一事务插入新的数据
仍然假设A与C同时向B转账
事务一:B多次查询自己今日的所有交易记录(查询多条记录)
事务二:A向B转账200元
事务三:C向B转账100元
则可能会出现以下的执行状况:
事务一(B查询记录) | 事务二(A向B转账200元) | 事务三(C向B转账100元) | |
---|---|---|---|
1 | 开启事务一 | ||
2 | 开启事务二 | ||
3 | 开启事务三 | ||
4 | 查询记录:0条 | ||
5 | A向B转账200元 | ||
6 | 提交事务 | ||
7 | 查询记录:1条 | ||
8 | C向B转账100元 | ||
9 | 查询记录:1条 | ||
10 | 提交事务 | ||
11 | 查询记录:2条 |
事务一在执行时,事务二和事务三有插入新的数据,导致事务一在多次查询的结果行数都有所不同,这就是幻读
3.事务的隔离级别
先说说提交:
只有insert(增),delete(删),update(改)才涉及提交
select(查)不涉及提交
3.1.默认 - Default
- Oracle:读已提交(二级隔离:Read committed)
- Mysql:可重复读(三级隔离:Repeatable read)
3.2.读未提交 - Read uncommitted
最低级的隔离级别,事务可以读取另一事务未提交的数据
可能出现脏读、不可重复读和幻读
事务一(B反复查询余额) | 事务二(A向B转账) | |
---|---|---|
1 | 事务开启 | |
2 | 事务开启 | |
3 | 查询余额:200 | |
4 | A向B转账2000元 | |
5 | 查询余额:2200(脏读) | |
6 | A撤销转账,修改转账额为200元 | |
7 | 查询余额:400 | |
8 | 提交事务 | |
9 | 查询余额:400 |
3.3.读已提交 - Read committed
二级隔离,只读取已提交的数据,未提交则不读
有效避免脏读,但无法解决不可重复读和幻读
3.4.可重复读 - Repeatable read(MySQL默认
)
三级隔离,当事务A开启,则不允许其他事务对事务A占用的数据进行修改
实现原理是事务A对第一次读取到的数据上行锁
事务一(B反复查询余额) | 事务二(A向B转账200元) | |
---|---|---|
1 | 事务开启 | |
2 | 事务开启 | |
3 | 查询余额:200元 | |
4 | A尝试向B转账200元,但因上锁被拒绝 报出异常,事务终止/回滚 | |
5 | 查询余额:200元 |
这里事务是终止/回滚,可以在service层中捕获异常,发生异常时递归,就能实现事务二等待事务一完成后再执行
有效避免脏读、不可重复读,但仍可insert,因此无法避免幻读
3.5.串行化 - Serializable
最高级隔离,读取时加“读锁”,写入时加“写锁”,一般是上范围锁
如果出现读写锁冲突,则后开启事务必须等待先开启事务全部执行完毕方可执行:
读写锁冲突有三种情况:
- 事务A读取数据加“读锁”,事务B尝试写入数据,此时 读 - 写冲突
- 事务A写入数据加“写锁”,事务B尝试读取数据,此时 写 - 读冲突
- 事务A写入数据加“写锁”,事务B尝试写入数据,此时 写 - 写冲突
事务100%隔离,避免脏读、不可重复读和幻读,但代价太大,通常不用