事务特性,事务的隔离级别以及spring中定义的事务传播行为

什么是事务?

事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。


事务特性

原子性 (atomicity):强调事务的不可分割,组成事务操作的各个最小逻辑单元缺一不可,强调了操作的完整性
一致性 (consistency):事务的执行前后,数据的完整性保持一致,强调数据的完整性
隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰,强调事务的独立性
持久性(durability) :事务一旦结束,数据应持久到数据库中,强调了事务的可持久性


不同的隔离性引发的安全性问题

脏读 :一个事务读取到了另一个事务的未提交的数据。这些数据可能会被提交,也可能不会被提交。提交了还好,如果未提交,就读取到了脏数据。
不可重复读 :一个事务读取到了另一个事务已经提交了的 update 数据,导致多次查询结果不一致.。侧重点是更新,重复读取数据时,若有其它事务对这些数据进行了更新操作,会导致查询到数据不一致。
幻读 :一个事务读取到了另一个事务已经提交了的 insert 数据,导致多次查询结果不一致。侧重点是插入,重复读取数据时,若有其它事务进行了插入操作,会导致查询到数据条数不一致。


事务的隔离级别

读取未提交(read uncommited):脏读,不可重复读,幻读都有可能发生。
读取已提交 (read commited):避免了脏读,但是不可重复读和幻读都有可能发生。
可重复读 (repeatable read):避免了脏读和不可重复读,但是幻读有可能发生。
串行化 (serializable):避免了以上问题,但效率较低。

Mysql 默认的事务隔离级别:可重复读
Oracle 默认的事务隔离级别:读取已提交


spring中定义的事务传播行为

* 保证同一个事务中
PROPAGATION_REQUIRED:支持当前事务,如果不存在,就新建一个事务(默认属性)
PROPAGATION_SUPPORTS:支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY:支持当前事务,如果不存在,抛出异常

* 保证没有在同一个事务中
PROPAGATION_REQUIRES_NEW:如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED :以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER :以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED: 如果当前事务存在,则嵌套事务执行


事务传播行为的解读

ServiceA { 
    static void methodA() 
        ServiceB.methodB(); 
    } 


ServiceB { 
    static void methodB() {
    } 
}

PROPAGATION_REQUIRED (加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务)

比如说:

ServiceB.methodB()的事务级别定义为PROPAGATION_REQUIRED,那么由于执行ServiceA.methodA的时候,ServiceA.methodA()已经起了事务,这时调用ServiceB.methodB(),ServiceB.methodB()看到自己已经运行在ServiceA.methodA()的事务内部,就不会再起新的事务。

而假如ServiceA.methodA()运行的时候发现自己没有在事务中,他就会为自己分配一个事务。这样,在ServiceA.methodA()或者在ServiceB.methodB()内的任何地方出现异常,事务都会被回滚。即ServiceB.methodB()的事务已经被提交,但是ServiceA.methodA()在接下来fail要回滚,ServiceB.methodB()也要回滚。

PROPAGATION_SUPPORTS(如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行)

和平常不加事务的代码区别很小,不多做描述了。

PROPAGATION_MANDATORY(必须在一个事务中运行)

也就是说:

他只能被一个父事务调用。否则,抛出异常。

PROPAGATION_REQUIRES_NEW(如果有事务存在,挂起当前事务,创建一个新的事务)

比如说:

我们设计ServiceA.methodA()的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB()的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB()的时候,ServiceA.methodA()所在的事务就会挂起,ServiceB.methodB()会起一个新的事务,等待ServiceB.methodB()的事务完成以后,他才继续执行。

他与PROPAGATION_REQUIRED的事务区别在于事务的回滚程度了。因为ServiceB.methodB()是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB()已经提交,那么ServiceA.methodA()失败回滚,ServiceB.methodB()是不会回滚的。如果ServiceB.methodB()失败回滚,如果他抛出的异常被ServiceA.methodA()捕获,ServiceA.methodA()事务仍然可能提交。

PROPAGATION_NOT_SUPPORTED(以非事务方式运行,如果有事务存在,挂起当前事务)

比如说:

ServiceA.methodA()的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB()的事务级别是PROPAGATION_NOT_SUPPORTED ,那么当执行到ServiceB.methodB()时,ServiceA.methodA()的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA()的事务。

PROPAGATION_NEVER(以非事务方式运行,如果有事务存在,抛出异常)

假设ServiceA.methodA()的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB()的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB()就要抛出异常了。

PROPAGATION_NESTED(如果当前事务存在,则嵌套事务执行)

ServiceA { 
    void methodA() 
        try { 
            //savepoint 
            ServiceB.methodB(); //PROPAGATION_NESTED 级别 
        } catch (SomeException) { 
            // 执行其他业务, 如 
            ServiceC.methodC(); 
        } 
    } 
}

理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。

也就是说,如果父事务最后回滚,他也要回滚的。而Nested事务的好处是他有一个savepoint。也就是说ServiceB.methodB()失败回滚,那么ServiceA.methodA()也会回滚到savepoint点上,ServiceA.methodA()可以选择另外一个分支,比如ServiceC.methodC(),继续执行,来尝试完成自己的事务。

posted @ 2020-03-16 22:17  丶沉梦昂志丶  阅读(212)  评论(0编辑  收藏  举报