数据库的隔离级别和传播行为
spring的声明式事务给事务的操作带来了很多方便。在这里回顾了一下隔离级别和传播行为的相关内容。先看看@Transaction:
@Transactional注解源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
//通过bean name指定事务管理器
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
//指定事务的传播行为
Propagation propagation() default Propagation.REQUIRED;
//指定隔离级别
Isolation isolation() default Isolation.DEFAULT;
//指定超时时间(单位:秒)
int timeout() default -1;
//是否只读事务
boolean readOnly() default false;
//默认发生所有的异常都会回滚,你可通过下面方法告诉spring发送特定异常是不用回滚:
//方法在发生指定名称的异常时回滚
Class<? extends Throwable>[] rollbackFor() default {};
//方法在发生指定名称的异常时回滚
String[] rollbackForClassName() default {};
//方法在发生指定名称的异常时不回滚
Class<? extends Throwable>[] noRollbackFor() default {};
//方法在发生指定名称的异常时不回滚
String[] noRollbackForClassName() default {};
}
在这里聊聊这里面的隔离级别和传播行为。
先提一下数据库事务的4个基本特征ACID:
Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)
这里的隔离性就是数据库定义的一个概念,当多个应用线程去操作同一数据时,可能发生脏读、不可重复读、幻读这三种问题。假如我们不考虑事务的隔离性,当并发发生时上述三种问题就很可能会发生,但我们想要完全杜绝上述问题,就要保证对同一数据只能有一个事务存在,也就是加锁,当已经有人在操作时,其他事务都要阻塞等待,这样就会造成性能太渣。在性能和数据一致性之间数据库定义了四种策略,将选择权给了用户,你想要性能还是要数据一致性?这四种策略分别是:未提交读、读写提交、可重复读、串行化,这四种隔离级别解决了那些问题:
隔离级别 | 脏 读 | 不可重复读 | 幻 读 |
未提交读 | √ | √ | √ |
读写提交 | × | √ | √ |
可重复读 | × | × | √ |
串行化 | × | × | × |
先说一下,这三种问题分别是什么:
脏读:脏读意味着一个事务读取了另一个事务未提交的数据,而这个数据是有可能回滚。例如:
两个事务同时在操作同一个数据时,一个事务在查询,很可能查询到的是其他事务未提交的数据,也就是脏数据,这就发生了脏读。
不可重复读:在一个事务范围内两个相同的查询却返回了不同数据。例如:
A用户读取该数据为1,B用户去修改了这个数据为2,B提交了该数据,这时A认为数据应该是1,A去修改这个数据,造成操作失败,这就是不可重复读。
幻读:是指当事务不是独立执行时发生的一种现象,例如:
第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
针对这三种问题数据库提出四个策略(隔离级别):
未提交读:可以读取别人未提交的数据,相当于不考虑三种问题的发生,无所谓。
独写提交:只能读取被提交的数据,避免了脏读问题,但可能发生不可重复读和幻读问题。
可重复读:有一个事务在对这个数据进行读写操作时,其他对这个数据操作的事务阻塞等待,避免了不可重复读的问题,但可能 发生幻读问题。
串行化:当有一个事务在执行的过程中,把其他所有的事务都阻塞掉,都等着,一个一个来,我怕乱。(都等去吧)
为了保证数据的一致性,不得不对数据加锁。隔离级别越高,性能就会直线的下降。不同的数据库对隔离级别的支持是不同的,Oracle支持:读写提交和串行化,默认是读写提交,MySQL四种都支持,默认是可重复读。
在每一个@Transactional上指定隔离界别太麻烦,也可以在Spring的配置文件中添加一个默认的隔离级别。
在聊聊传播行为:
传播行为是Spring存在的一个概念,每个事务方法(也就是标记有@Transaction的方法)都有他自己的事务策略(隔离级别等等),当一个事务方法调用另一个事务方法时,可能一个有事务一个没事务,也可能都有事务或者都没事务,这时应该采用那种策略?Spring提供了7中传播行为供你选择(第一种是默认的,第二种要看一下,其他的我认为很少能用上):
REQUIRED:主方法有事务就用该事务,没有就就看子方法有没有事务。(主方法没事务,子方法有事务,那子方法的事务不传递给主方法。)
NESTRD:当前方法调用子方法时,如果子方法发生异常,只回滚子方法的事务,而不回滚当前的方法。(事务之间相互独立)
SUPPORTS:如果当前存在事务,就沿用当前事务,如果不存在事务,则采用无事务的方式执行子方法。(忽略子方法事务,只看主方法是否有事务)
MANDATORY:主方法没有事务就会抛出异常,有事务就使用主方法事务。
REQUIRES_NEW:无论当前事务是否存在,都会创建新事务运行方法。
NOT_SUPPORTED:不支持事务,不开启事务,运行方法。
NEVER:不支持事务,有事务的话,就抛异常。
这7中传播行为定义在Propagation这个枚举类中。