事务是逻辑处理原⼦性的保证⼿段,通过使⽤事务控制,可以极⼤的避免出现逻辑处理失败导致的脏数据等问题。
事务最重要的两个特性,是事务的传播级别和数据隔离级别。传播级别定义的是事务的控制范围,事务隔离级别定义的是事务在数据库读写⽅⾯的控制范围。
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,嵌套级别事务。该传播级别特征是,如果上下⽂中存在事务,则嵌套事务执⾏,如果不存在事务,则新建事务
1、Serializable :最严格的级别,事务串⾏执⾏,资源消耗最⼤;
2、REPEATABLE READ :保证了⼀个事务不会修改已经由另⼀个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
3、READ COMMITTED :⼤多数主流数据库的默认事务等级,保证了⼀个事务不会读到另⼀个并⾏事务已修改但未提交的数据,避免了“脏读取”。该级别适⽤于⼤多数系统,在保证性能的情况下,又保证了一定的安全性。
4、Read Uncommitted :保证了读取过程中不会读取到⾮法数据。
上⾯的解释其实每个定义都有⼀些拗⼝,其中涉及到⼏个术语:脏读、不可重复读、幻读。
这⾥解释⼀下:
脏读 :所谓的脏读,其实就是读到了别的事务回滚前的脏数据。⽐如事务B执⾏过程中修改了数据X,在未提交前,事务A读取了X,⽽事务B却回滚了,这样事务A就形成了脏读。
不可重复读 :不可重复读字⾯含义已经很明了了,⽐如事务A⾸先读取了⼀条数据,然后执⾏逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。
幻读 :⼩的时候数⼿指,第⼀次数⼗10个,第⼆次数是11个,怎么回事?产⽣幻觉了?
幻读也是这样⼦,事务A⾸先根据条件索引得到10条数据,然后事务B改变了数据库⼀条数据,导致也
符合事务A当时的搜索条件,这样事务A再次搜索发现有11条数据了,就产⽣了幻读。
spring事务隔离级别以及和MySQL事务的隔离级别是一模一样的。
在分布式的⾼并发环境下,对于核⼼业务逻辑的检查,要采⽤加锁机制。
⽐如事务开启需要读取⼀条数据进⾏验证,然后逻辑操作中需要对这条数据进⾏修改,最后提交。
这样的⼀个过程,如果读取并验证的代码放到事务之外,那么读取的数据极有可能已经被其他的事务修改,当前事务⼀旦提交,⼜会重新覆盖掉其他事务的数据,导致数据异常。
所以在进⼊当前事务的时候,必须要将这条数据锁住,使⽤for update就是⼀个很好的在分布式环境下的控制⼿段。
⼀种好的实践⽅式是使⽤编程式事务⽽⾮声明式,尤其是在较为规模的项⽬中。对于事务的配置,在代码量⾮常⼤的情况下,将是⼀种折磨,⽽且⼈⾁的⽅式,绝对不能避免这种问题。
将DAO保持针对⼀张表的最基本操作,然后业务逻辑的处理放⼊manager和service中进⾏,同时使⽤编程式事务更精确的控制事务范围。
特别注意的,对于事务内部⼀些可能抛出异常的情况,捕获要谨慎,不能随便的catch Exception 导致事务的异常被吃掉⽽不能正常回滚。