Spring五个事务隔离级别和七个事务传播行为

来源:https://yq.aliyun.com/articles/48893

 

Spring五个事务隔离级别和七个事务传播行为

1. 脏读 :脏读就是指当一个事务正在访问数据,而且对数据进行了修改,而这种修改尚未提交到数据库中,这时,另一个事务也访问这个数据,而后使用了这个数据。面试

2. 不可重复读 :是指在一个事务内,屡次读同一数据。在这个事务尚未结束时,另一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,因为第二个事务的修改,那么第一个事务两次读到的的数据多是不同的。这样就发生了在一个事务内两次读到的数据是不同的,所以称为是不 可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,做者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。若是 只有在做者所有完成编写后编辑人员才能够读取文档,则能够避免该问题。spacer.gif …数据库事务和Spring事务是通常面试都会被提到,不少朋友写惯了代码,不多花时间去整理概括这些东西,结果原本会的东西,竟然吞吞吐吐答不上来。spring

下面是我收集到一些关于Spring事务的问题,但愿能帮助你们过关。数据库

3. 幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的所有数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,之后就会发生操做第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉同样。例如,一个编辑人员更改做者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现做者已将未编辑的新材料添加到该文档中。 若是在编辑人员和生产部门完成对原始文档的处理以前,任何人都不能将新材料添加到文档中,则能够避免该问题。编程

补充 : 基于元数据的 Spring 声明性事务 :安全

Isolation 属性一共支持五种事务设置,具体介绍以下:并发

<!—->l          <!—->DEFAULT 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 .分布式

<!—->l          <!—->READ_UNCOMMITTED 会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )高并发

<!—->l          <!—->READ_COMMITTED  会出现不可重复读、幻读问题(锁定正在读取的行)性能

<!—->l          <!—->REPEATABLE_READ 会出幻读(锁定所读取的全部行)spa

<!—->l          <!—->SERIALIZABLE 保证全部的状况不会发生(锁表)

不可重复读的重点是修改 : 
一样的条件 ,   你读取过的数据 ,   再次读取出来发现值不同了 
幻读的重点在于新增或者删除 
一样的条件 ,   第 1 次和第 2 次读出来的记录数不同

Spring在TransactionDefinition接口中定义这些属性

在TransactionDefinition接口中定义了五个不一样的事务隔离级别

ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应 
ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务能够看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读

ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另一个事务读取。另一个事务不能读取该事务未提交的数据。这种事务隔离级别能够避免脏读出现,可是可能会出现不可重复读和幻像读。

ISOLATION_REPEATABLE_READ 这种事务隔离级别能够防止脏读,不可重复读。可是可能出现幻像读。它除了保证一个事务不能读取另外一个事务未提交的数据外,还保证了避免下面的状况产生(不可重复读)。

ISOLATION_SERIALIZABLE 这是花费最高代价可是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

 

在TransactionDefinition接口中定义了七个事务传播行为。

PROPAGATION_REQUIRED 若是存在一个事务,则支持当前事务。若是没有事务则开启一个新的事务。

PROPAGATION_SUPPORTS 若是存在一个事务,支持当前事务。若是没有事务,则非事务的执行。可是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少量不一样。

PROPAGATION_MANDATORY 若是已经存在一个事务,支持当前事务。若是没有一个活动的事务,则抛出异常。

PROPAGATION_REQUIRES_NEW 老是开启一个新的事务。若是一个事务已经存在,则将这个存在的事务挂起。

PROPAGATION_NOT_SUPPORTED 老是非事务地执行,并挂起任何存在的事务。

PROPAGATION_NEVER 老是非事务地执行,若是存在一个活动事务,则抛出异常

PROPAGATION_NESTED若是一个活动的事务存在,则运行在一个嵌套的事务中. 若是没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

事务是逻辑处理原子性的保证手段,经过使用事务控制,能够极大的避免出现逻辑处理失败致使的脏数据等问题。

事务最重要的两个特性,是事务的传播级别和数据隔离级别。传播级别定义的是事务的控制范围,事务隔离级别定义的是事务在数据库读写方面的控制范围。

事务的7种传播级别:

1) PROPAGATION_REQUIRED ,默认的spring事务传播级别,使用该级别的特色是,若是上下文中已经存在事务,那么就加入到事务中执行,若是当前上下文中不存在事务,则新建事务执行。因此这个级别一般能知足处理大多数的业务场景。

2)PROPAGATION_SUPPORTS ,从字面意思就知道,supports,支持,该传播级别的特色是,若是上下文存在事务,则支持事务加入事务,若是没有事务,则使用非事务的方式执行。因此说,并不是全部的包在transactionTemplate.execute中的代码都会有事务支持。这个一般是用来处理那些并不是原子性的非核心业务逻辑操做。应用场景较少。

3)PROPAGATION_MANDATORY , 该级别的事务要求上下文中必需要存在事务,不然就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。好比一段代码不能单独被调用执行,可是一旦被调用,就必须有事务包含的状况,就可使用这个传播级别。

4)PROPAGATION_REQUIRES_NEW ,从字面便可知道,new,每次都要一个新事务,该传播级别的特色是,每次都会新建一个事务,而且同时将上下文中的事务挂起,执行当前新建事务完成之后,上下文事务恢复再执行。

这是一个颇有用的传播级别,举一个应用场景:如今有一个发送100个红包的操做,在发送以前,要作一些系统的初始化、验证、数据记录操做,而后发送100封红包,而后再记录发送日志,发送日志要求100%的准确,若是日志不许确,那么整个父事务逻辑须要回滚。
怎么处理整个业务需求呢?就是经过这个PROPAGATION_REQUIRES_NEW 级别的事务传播控制就能够完成。发送红包的子事务不会直接影响到父事务的提交和回滚。

5)PROPAGATION_NOT_SUPPORTED ,这个也能够从字面得知,not supported ,不支持,当前级别的特色就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。

这个级别有什么好处?能够帮助你将事务很可能的缩小。咱们知道一个事务越大,它存在的风险也就越多。因此在处理事务的过程当中,要保证尽量的缩小范围。好比一段代码,是每次逻辑操做都必须调用的,好比循环1000次的某个非核心业务逻辑操做。这样的代码若是包在事务中,势必形成事务太大,致使出现一些难以考虑周全的异常状况。因此这个事务这个级别的传播级别就派上用场了。用当前级别的事务模板抱起来就能够了。

6)PROPAGATION_NEVER ,该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制中止执行!这个级别上辈子跟事务有仇。

7)PROPAGATION_NESTED ,字面也可知道,nested,嵌套级别事务。该传播级别特征是,若是上下文中存在事务,则嵌套事务执行,若是不存在事务,则新建事务。

那么什么是嵌套事务呢?不少人都不理解,我看过一些博客,都是有些理解误差。

嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务以前,父事务创建一个回滚点,叫save point,而后执行子事务,这个子事务的执行也算是父事务的一部分,而后子事务执行结束,父事务继续执行。重点就在于那个save point。看几个问题就明了了:

若是子事务回滚,会发生什么?

父事务会回滚到进入子事务前创建的save point,而后尝试其余的事务或者其余的业务逻辑,父事务以前的操做不会受到影响,更不会自动回滚。

若是父事务回滚,会发生什么?

父事务回滚,子事务也会跟着回滚!为何呢,由于父事务结束以前,子事务是不会提交的,咱们说子事务是父事务的一部分,正是这个道理。那么:

事务的提交,是什么状况?

是父事务先提交,而后子事务提交,仍是子事务先提交,父事务再提交?答案是第二种状况,仍是那句话,子事务是父事务的一部分,由父事务统一提交。

如今你再体会一下这个”嵌套“,是否是有那么点意思?

以上是事务的7个传播级别,在平常应用中,一般能够知足各类业务需求,可是除了传播级别,在读取数据库的过程当中,若是两个事务并发执行,那么彼此之间的数据是如何影响的呢?

这就须要了解一下事务的另外一个特性:数据隔离级别

数据隔离级别分为不一样的四种:

一、Serializable :最严格的级别,事务串行执行,资源消耗最大;

二、REPEATABLE READ :保证了一个事务不会修改已经由另外一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的状况,可是带来了更多的性能损失。

三、READ COMMITTED :大多数主流数据库的默认事务等级,保证了一个事务不会读到另外一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。

四、Read Uncommitted :保证了读取过程当中不会读取到非法数据。
 
上面的解释其实每一个定义都有一些拗口,其中涉及到几个术语:脏读、不可重复读、幻读。
这里解释一下:
 
脏读 :所谓的脏读,其实就是读到了别的事务回滚前的脏数据。好比事务B执行过程当中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就造成了脏读。
 
不可重复读 :不可重复读字面含义已经很明了了,好比事务A首先读取了一条数据,而后执行逻辑的时候,事务B将这条数据改变了,而后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。
 
幻读 :小的时候数手指,第一次数十10个,第二次数是11个,怎么回事?产生幻觉了?
幻读也是这样子,事务A首先根据条件索引获得10条数据,而后事务B改变了数据库一条数据,致使也符合事务A当时的搜索条件,这样事务A再次搜索发现有11条数据了,就产生了幻读。
 
一个对照关系表:
                                       Dirty reads          non-repeatable reads            phantom reads
Serializable                          不会                        不会                                           不会
REPEATABLE READ             不会                        不会                                            会
READ COMMITTED             不会                        会                                                会
Read Uncommitted             会                           会                                                会
 
因此最安全的,是Serializable,可是伴随而来也是高昂的性能开销。
另外,事务经常使用的两个属性:readonly和timeout
一个是设置事务为只读以提高性能。
另外一个是设置事务的超时时间,通常用于防止大事务的发生。仍是那句话,事务要尽量的小!

最后引入一个问题:
一个逻辑操做须要检查的条件有20条,可否为了减少事务而将检查性的内容放到事务以外呢?

不少系统都是在DAO的内部开始启动事务,而后进行操做,最后提交或者回滚。这其中涉及到代码设计的问题。小一些的系统能够采用这种方式来作,可是在一些比较大的系统,
逻辑较为复杂的系统中,势必会将过多的业务逻辑嵌入到DAO中,致使DAO的复用性降低。因此这不是一个好的实践。

来回答这个问题:可否为了缩小事务,而将一些业务逻辑检查放到事务外面?答案是:对于核心的业务检查逻辑,不能放到事务以外,并且必需要做为分布式下的并发控制!
一旦在事务以外作检查,那么势必会形成事务A已经检查过的数据被事务B所修改,致使事务A徒劳无功并且出现并发问题,直接致使业务控制失败。
因此,在分布式的高并发环境下,对于核心业务逻辑的检查,要采用加锁机制。
好比事务开启须要读取一条数据进行验证,而后逻辑操做中须要对这条数据进行修改,最后提交。
这样的一个过程,若是读取并验证的代码放到事务以外,那么读取的数据极有可能已经被其余的事务修改,当前事务一旦提交,又会从新覆盖掉其余事务的数据,致使数据异常。
因此在进入当前事务的时候,必需要将这条数据锁住,使用for update就是一个很好的在分布式环境下的控制手段。

一种好的实践方式是使用编程式事务而非生命式,尤为是在较为规模的项目中。对于事务的配置,在代码量很是大的状况下,将是一种折磨,并且人肉的方式,绝对不能避免这种问题。
将DAO保持针对一张表的最基本操做,而后业务逻辑的处理放入manager和service中进行,同时使用编程式事务更精确的控制事务范围。
特别注意的,对于事务内部一些可能抛出异常的状况,捕获要谨慎,不能随便的catch Exception 致使事务的异常被吃掉而不能正常回滚。

Spring配置声明式事务:

* 配置DataSource
* 配置事务管理器
* 事务的传播特性
* 那些类那些方法使用事务

Spring配置文件中关于事务配置老是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,不管哪一种配置方式,通常变化的只是代理机制这部分。

    DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,好比使用Hibernate进行数据访问 时,DataSource实际为SessionFactory,TransactionManager的实现为 HibernateTransactionManager。

根据代理机制的不一样,Spring事务的配置又有几种不一样的方式:

第一种方式:每一个Bean都有一个代理

 第二种方式:全部Bean共享一个代理基类

第三种方式:使用拦截器

第四种方式:使用tx标签配置的拦截器

第五种方式:全注解

一、spring事务控制放在service层,在service方法中一个方法调用service中的另外一个方法,默认开启几个事务?

spring的事务传播方式默认是PROPAGATION_REQUIRED,判断当前是否已开启一个新事务,有则加入当前事务,不然新开一个事务(若是没有就开启一个新事务),因此答案是开启了一个事务。

二、spring 什么状况下进行事务回滚?

Spring、EJB的声明式事务默认状况下都是在抛出unchecked exception后才会触发事务的回滚

unchecked异常,即运行时异常runntimeException 回滚事务;

checked异常,即Exception可try{}捕获的不会回滚.固然也可配置spring参数让其回滚.

spring的事务边界是在调用业务方法以前开始的,业务方法执行完毕以后来执行commit or rollback(Spring默认取决因而否抛出runtime异常).若是抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。通常不须要在业务方法中catch异常,若是非要catch,在作完你想作的工做后(好比关闭文件等)必定要抛出runtime exception,不然spring会将你的操做commit,这样就会产生脏数据.因此你的catch代码是多此一举。

相关文章
相关标签/搜索