隔离级别(isolation)定义了事务并发的隔离程度。
数据隔离级别分为不同的四种:
上面的解释其实每个定义都有一些拗口,其中涉及到几个术语:脏读、不可重复读、幻读。
这里解释一下:
不可重复读的重点是修改:
读的重点在于新增或者删除:
从控制的角度来看, 两者的区别就比较大:
一个对照关系表: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 导致事务的异常被吃掉而不能正常回滚。