Spring事务的隔离级别与传播行为
什么是事务
是数据操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么全部执行,要么都不执行(也就是说要么全部成功,要么全部失败);事务是一组不可再分割的操作集合(工作逻辑单元)。
常见的例子就是转账,例如A给B转账1000元
开启事务 -> A账户扣除1000 -> B账户增加1000 -> 提交事务
上面的事务任何一个步骤出错都会导致事务回滚
事务的四大特性(ACID)
-
原子性(Atomicity):一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。例如转账,要么成功,要么失败,不存在中间状态。
利用InnoDB的undo log(回滚日志),它是实现原子性的关键,事务回滚时能够撤销所有已经执行成功的sql语句,它需要记录要回滚的相应日志信息。 -
隔离性(Isolation):多个事务并发执行的时候,事务内部的操作与其它事务是隔离的,并发执行的各个事务之间不能互相干扰。
例:A账户有200元,分成两个事务向B转账两次50元,并发执行,若前一个事务还未把50元写回磁盘就被后一个事务读取到,两个事务读取到的B的余额都0元,最终B一共就只增加了50元。
锁和MVCC -
持久性(Durability):事务一旦提交它对数据库的改变是永久性的,接下来的任何操作或故障对其不会有任何影响。
利用InnnoDB的redo log,当有数据在进行修改时,不仅会在内存中进行操作,还会再redo log中记录这次操作。事务提交时,会将redo log进行刷盘。数据库宕机重启时,会将redo log中的内容恢复到数据库中。 -
一致性(Consistency):事务的执行结果必须是使数据库从一个一致性状态变到另一个一致性状态(事务执行前后,数据处于一种合法的状态。也就是说满足自己预定的约束,这个数据就是合法的)。
例:A向B转账50元,A扣除了50元,可是B由于种种原因没有收到这50元,此时这50元凭空消失了。这显然是不合法的,因为自己预定了约束:不论转账成功与否,参与转账的两个账户余额总和是不变的。
显然,C是目的,AID都是为了达到C的手段,数据库必须满足AID三大特性,才有可能实现一致性;由于自身的原因也是无法达到一致性的,例如A扣了钱,故意不给B加钱。
JDBC事务处理
try{
// 开启事务
connection.setAutoCommit(false);
// 一些sql操作 insert update delete...
// 提交事务
connection.commit();
} catch(Exception e) {
// 异常回滚事务
connection.rollback();
}
事务并发带来的问题
- 脏读:事务A读取到事务B未提交的数据;
- 不可重复读:一个事务中对某个数据读取了两次,两次读取到的结果不一致;
- 幻读:一个事务中,同一查询条件下,第二次查询出现了第一次查询所没有的数据;
Spring事务隔离级别
事务的隔离级别也就是为了解决事务并发带来的各种问题,以下是Spring提供的事务隔离级别枚举类,可以看到Spring提供了五种事务隔离级别:
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}
- DEFAULT(默认):使用数据库默认的隔离级别,InnoDB存储引擎默认为RR,即可重复读;
- READ_UNCOMMITTED(读未提交):事务最低的隔离级别,能够读取到没有被提交的数据,这种隔离级别会产生脏读、不可重复读、幻读;(读不加锁,写会加行级共享锁)
- READ_COMMITTED(读已提交):A事务能够读取到B事务提交之后的数据。能够避免脏读的出现,但是会产生不可重复读、幻读;(写加行级排他锁,事务结束才会释放,读加行级共享锁,读到数据便会释放,如A事务读到数据之后释放锁,B事务则可以修改该数据,A再读的时候数据就不一样了,则会产生不可重复读)。
- REPEATABLE_READ(可重复读):可以重复读取,一个事务中对同一数据的读取结果是不变的。能够避免脏读、不可重复读,但是会产生幻读;(读取数据时加行级共享锁、行级排他锁,事务结束才会释放,无法控制插入数据,因此会产生幻读)
- SERIALIZABLE(串行化):最高的事务隔离级别,完全串化执行,毫无并发可言,性能极低,采用了表级共享锁与排他锁(事务结束释放),能够避免脏读、不可重复读、幻读。
那么隔离级别越高越好咯?答案肯定是否定的,隔离级别越高,效率也就越低。
Spring事务的传播行为
单独一个事务不可以传播,所以,事务的传播行为是当一个事务方法被另外一个事务方法调用时,这个事务方法应该如何进行,以下是Spring事务传播行为的枚举类,这里可以看到,Spring定义了七种事务的传播行为。
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
- REQUIRED:如果当前存在事务,则加入到这个事务,如果当前没有事务,则新建一个事务,这个是Spring默认的传播行为。
如果当前存在事务A,此时调用事务B,B会和A一起提交,无论是A还是B中发生的异常都会引起A、B的操作一起回滚。
package com.mezjh.blog.spring.transaction.propagationbehavior;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation