啥是事务
事务就是你有一连串的操作,每个操作都必须成功完成。如果其中有一个操作失败就全部回滚,之前操作的所有更改都会被取消。
举例来说,你现在有一个学生表,一个学生成绩表。当一个学生a被开除时,就需要同时删除这两个表中关于a的相关数据。当你在学生表中删除了a,但是在成绩表中删除a的成绩失败了,那么学生表中的a就会被恢复。
事务必须满足4个特点
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
不同隔离级别带来的不同问题
第一类丢失更新
某个事务的回滚导致另一个事务已更新的数据丢失了。
时刻 | 事务一 | 事务二 |
---|---|---|
T1 | 读取 a=10 | |
T2 | 读取a=10 | |
T3 | 更改 a=11 | |
T4 | 提交 a=11 | |
T5 | 更改 a=11失败 | |
T6 | 回滚 a=10 |
上表中事务一已经提交了a=11,但是在因为事务二的回滚a又变成了10。
第二类丢失更新
某个事务的提交导致另一个事务已更新的数据丢失了。
时刻 | 事务一 | 事务二 |
---|---|---|
T1 | 读取 a=10 | |
T2 | 读取a=10 | |
T3 | 执行a=a+1 | |
T4 | 提交 a=11 | |
T5 | 执行a=a+1 | |
T6 | 提交 a=11 |
上表中事务一和事务二都执行了a=a+1,但是a的值从10变成了11而不是12。
脏读
某个事务读取了另一个事务未提交的数据。
时刻 | 事务一 | 事务二 |
---|---|---|
T1 | 读取 a=10 | |
T2 | ||
T3 | 执行a=a+1 | |
T4 | 读取a=11 | |
T5 | 提交失败回滚a=10 |
上表中事务一的回滚导致事务二读取到了错误的数据
幻读
某个事务对同一个表前后两次查询到的行数不一样。假设有个表总共有3条数据
时刻 | 事务一 | 事务二 |
---|---|---|
T1 | select count(id) (查到3条) | |
T2 | insert id=2 (插入一条) | |
T3 | commit(提交插入操作) | |
T4 | select count(id) (查到4条) |
上表中事务一的插入导致事务二的两次查询结果不一致
不可重复读
某个事务对同一个数据前后读取的值不一致。
时刻 | 事务一 | 事务二 |
---|---|---|
T1 | 读取a=10 | |
T2 | 读取a=10 | |
T3 | 执行a=a+1 | |
T4 | 提交a=11 | |
T4 | 读取a==11 |
上表中事务一对a的修改导致事务二的两次查询结果不一致
应对方法
我们可以设置不同的隔离级别来解决这些问题。在Spring中可以在方法前声明 @Transactional(isolation = , propagation = )
来表示对这个方法启用事务,其中isolation
表示隔离级别,propagation
表示传播方式。
isolation(隔离级别)
isolation一共有4个值,封装在枚举类Isolation
中。
隔离级别 | 第一类丢失更新 | 脏读 | 第二类丢失更新 | 不可重复读 | 幻读 |
---|---|---|---|---|---|
READ_UNCOMMITTED | Y | Y | Y | Y | Y |
READ_COMMITTED | N | N | Y | Y | Y |
REPEATABLE_READ | N | N | N | N | Y |
SERIALIZABLE | N | N | N | N | N |
安全性由上至下递增,速度由上至下递减。(越安全越慢)。
propagation(传播方式)
当你使用事务的方法调用另一个方法时,propagation就派上用场了。
REQUIRED(默认值):在有transaction状态下执行;如当前没有transaction,则创建新的transaction;(就是a方法调用了b方法,当b出错两个都回滚)
SUPPORTS:如当前有transaction,则在transaction状态下执行;如果当前没有transaction,在无transaction状态下执行;
MANDATORY:支持当前已经存在的事务,如果还没有事务,就抛出一个异常。如果上例中aMethod的传播性配置为MANDATORY,我们就无法在没有事务的情况下调用aMethod,因此,传播性为MANDATORY的方法必定是一个其他事务的子事务,当逻辑上独立存在没有意义或者可能违反数据、事务完整性的时候,就可以考虑设置这样的传播性设置。;
REQUIRES_NEW:创建新的transaction并执行;如果当前已有transaction,则将当前transaction挂起;(a调用b,如果b出错了抛出异常,b回滚。a有可能提交成功,如果a没有处理这个异常那么a回滚)
NOT_SUPPORTED:在无transaction状态下执行;如果当前已有transaction,则将当前transaction挂起;
NEVER:在无transaction状态下执行;如果当前已有transaction,则抛出异常IllegalTransactionStateException。(就是a方法调用了b方法,如果b有事务就抛异常)
NESTED:在当前事务中创建一个嵌套事务,如果还没有事务,那么就简单地创建一个新事务。
更多请见propagation
挖个坑
- 各个隔离级别生效原理