[MySQL][Spring] 事务隔离级别 与 Spring的事务传播 - Gukie/learning GitHub Wiki

refer

事务隔离级别

  • 未提交读(read uncommitted)

别的事务没提交的数据也可以被当前事务看到, 会出现脏读

  • 不可重复度(read committed)

只能读取到别的事务已经提交的数据,但是会出现不可重复度:

  • 当一个事务比较长,第一次读取到的数据是A
  • 而此时别的事务正在处理那条数据,并且那个事务在当前事务提交前提交
  • 这样如果当前事务在提交前再次读取数据A,得到的数据可能已经变为了A1,这就造成了不可重复读
  • 可重复度(repeatable read),这是MySQL默认的事务隔离级别

不会出现不可重复读的现象,但是却会出现幻读:

  • 当事务A在读取某个范围的数据时,事务B正好往那个范围插入了一些数据,并提交,当事务A再次读取那个范围内的数据时,数据已经跟之前的不一样了;
  • 幻读可以通过MVCC(Multiversion Concurrency Control)来解决
  • 串行(Serializable)

所有的事务都是串行处理的,这中隔离级别最安全,但带来的超时和锁竞争却是很严重的

MVCC(Multiversion Concurrency Control)

MySQL中的每行数据,默认会有两个隐藏的列,用于保存行的创建时的版本号和删除时的版本号 每开始一个新的事务时,系统版本号都会自增;事务开始时刻的版本号会拿来当作事务的版本号,用于事务的操作

select- 只读取创建版本号小于当前事务版本并且删除版本号大于当前版本的数据 updae- 会插入一个新行,将其版本号设置为当前事务的版本号,然后将原来行的删除版本号设置为当前事务的版本号 insert- 将新数据的创建版本号设置为当前事务的版本号 delete- 将数据的删除版本号设置为当前版本号


Spring的事务传播级别

首先需要注意的是,事务的方法要有效果即有事务的发生,需要该方法是被代理的

如果是同一个类中的非事务方法调用了事务方法,那么事务方法将不会有事务,因为是内部调用,事务方法不会被代理

Spring事务传播有7种

  • Propagation_required

如果处于事务中,就用;否则就创建一个新的事务

  • propagation_supports

有事务就以事务的方式进行,没有就以无事务的方式进行处理

  • propagation_mandatory

必须有一个父事务,否则抛异常

  • propagation_never

不能在事务中运行,否则抛异常

  • propagation_not_supported

不在事务中运行,如果有父事务,则将父事务挂起,直到自己的方法运行完成后,父事务才能继续

  • propagation_required_new

不管是否有父事务,都将启动一个新的事务;这样当前方法跟调用它的方法是处于不同的事务中的,并且如果有父事务,父事务会被挂起直到新事务完成为止

  • 使用场景:不管业务逻辑的service是否有异常,Log-servier都应该能够记录成功,所以Log-Service的传播属性可以配为此属性
  • propagation_nested

是父事务的一个子事务;会跟父事务一起提交

spring 事务传播的举例浅析 (从refer中copy过来的)

举例浅析Spring嵌套事务 ServiceA#methodA(我们称之为外部事务),ServiceB#methodB(我们称之为外部事务)

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

PROPAGATION_REQUIRED

假如当前正要执行的事务不在另外一个事务里,那么就起一个新的事务 比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候 1、如果ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。这时只有外部事务并且他们是共用的,所以这时ServiceA.methodA或者ServiceB.methodB无论哪个发生异常methodA和methodB作为一个整体都将一起回滚。 2、如果ServiceA.methodA没有事务,ServiceB.methodB就会为自己分配一个事务。这样,在ServiceA.methodA中是没有事务控制的。只是在ServiceB.methodB内的任何地方出现异常,ServiceB.methodB将会被回滚,不会引起ServiceA.methodA的回滚

PROPAGATION_SUPPORTS

如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行 PROPAGATION_MANDATORY 必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常 PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行. 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。 1、如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。 2、如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA的try..catch捕获并处理,ServiceA.methodA事务仍然可能提交;如果他抛出的异常未被ServiceA.methodA捕获处理,ServiceA.methodA事务将回滚。

使用场景: 不管业务逻辑的service是否有异常,Log Service都应该能够记录成功,所以Log Service的传播属性可以配为此属性。最下面将会贴出配置代码。

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

开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.

比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_NESTED,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的子事务并设置savepoint,等待ServiceB.methodB的事务完成以后,他才继续执行。。因为ServiceB.methodB是外部事务的子事务,那么 1、如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB也将回滚。 2、如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA的try..catch捕获并处理,ServiceA.methodA事务仍然可能提交;如果他抛出的异常未被ServiceA.methodA捕获处理,ServiceA.methodA事务将回滚。

理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是: PROPAGATION_REQUIRES_NEW 完全是一个新的事务,它与外部事务相互独立; 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.

在 spring 中使用 PROPAGATION_NESTED的前提:

  1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!!
  2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+
  3. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0

确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了.