事务隔离及事务传播机制

前言

谈到事务,那是面试必问的问题,事务比较抽象,他可以是一条sql,也可以是一段程序,如果没有事务这个概念,我们在编程过程中的复杂度会大大增加,比如一个很贴近生活的例子:

你的爸爸妈妈同时像你支付宝里转了500块钱,你爸爸的转账请求读到你当前钱包里有1000块钱,与此同时你妈妈的转账请求也读到了你钱包的余额,于是一起对余额进行操作,1000+500=1500,于是你的余额变成了1500块。

之后你爸爸打电话问:“儿子给你转了500块,收到了吗?”

再过了一会你妈妈也打电话过来问:“儿子给你转了500块,多买点吃的。”

这你就纳闷了,明明爸妈都给我转了500余额应该是2000才对,为什么才1500?

这就是脏读现象,当一个事务修改了数据还未提交的时候,另一个事务使用该数据。如果这个问题不处理那支付宝不是大乱套了。


事务的特性

  1. 原子性(atomicity):一个事务是不可分割的,一个事务里的操作,要么全部成功,要么全部失败。
  2. 一致性(consistency):一个事务执行前后数据的完整性保持一致。
  3. 隔离性(isolation):一个事务在执行过程中不该受到其他事务影响,每个事务是相互隔离的,互不影响。
  4. 持久性(durability):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

多事务并发下的问题

  1. 脏读:就是一个事务修改了数据,在未提交数据前,另一个事务使用了该数据。
  2. 不可重复读:就是一个事务第一次查看数据,比如说是3,然后一个事务进来修改为了5并提交了事务,第一次的事务再次读的时候发现变成了5就是不可重复读。
  3. 幻读:在对全表进行操作的时候,发现修改了100条记录,在此期间有另一个事务进来新增了一条数据,最后之前明明为100条的数据,发现还有没有被修改的数据行,就跟产生了幻觉一样。

数据库隔离级别

  1. 读未提交:数据库最低隔离级别,可能会发生脏读,不可重复读,幻读。
  2. 读已提交:不会发生脏读,可能发生不可重复读和幻读。
  3. 可重复读:可以避免脏读和不可重复读,无法避免幻读,mysql默认的隔离级别。
  4. 串行化:数据库读最高隔离级别,可以避免幻读,但是会导致大量超时现象和锁竞争。

Spring下的事务传播机制

事务传播行为,既然是传播肯定要有两个东西,才可能发生传播的行为,指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何执行。

传播行为 含义
Propagation_Required 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则会启动一个新事务。
Propagation_Supports 表示当前方法不需要事务上下文,但如果存在当前事务的话,那么该方法会在这个事务中运行。
Propagation_Mandatory 表示这个方法必须在事务中运行,如果事务不存在,则会抛出一个异常。
Propagation_Required_New 表示当前方法必须运行在自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务将会被挂起。
Propagation_Not_Supported 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。
Propagation_Nerver 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。
Propagation_Nested 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与Propagation_Required一样。

Propagation_Required:

默认到事务传播方式,指该方法必须在事务中运行,如果当前没有事务就创建一个事务,如果当前有事务就加入到当前事务中去。

1
2
3
4
5
6
7
8
9
10
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}

@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// do something
}

单独调用方法B的时候,会发现上下文中没有事务,则会开启一个事务,当调用方法A的时候,发现上下文环境中没有事务,就会新建一个事务,当调用到方法B的时候,发现已经有A事务的上下文环境就加入到A事务中。

Propagation_Supports:

指该方法不需要事务上下文环境,如果没有上下文环境就非事务执行,如果有上下文环境就加入到该事务中。

1
2
3
4
5
6
7
8
9
10
11
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}

// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// do something
}

单独调用方法B到时候是以非事务的方式执行方法B,当调用方法A的时候,方法B就加入了方法A的事务中事务的执行。

Propagation_Mandatory:

指方法必须在事务中进行,如果当前没有事务则会抛出异常

1
2
3
4
5
6
7
8
9
10
11
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}

// 事务属性为MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// do something
}

当单独调用方法B的时候,会因为没有事务的上下文,会抛出IllegalTransactionStateException异常,当调用方法A的时候就会加入事务A中。

Propagation_Required_New:

表示当前方法必须运行在自己的事务中,一个事务将被启动,如果存在当前事务,在方法执行期间,当前事务会被挂起。使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作为事务管理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
// do something else
}


// 事务属性为REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// do something
}

单独调用方法B的时候会新建一个事务运行,当调用方法A的时候,执行到B方法的时候会将事务A挂起,等B提交后,再执行doSomeThingB,如果doSomeThingB执行失败了不会回滚事务B,只会回滚事务A里除了事务B之外的东西。

Propagation_Not_Supported:

表示该方法不应该运行在事务上下文中,如果存在当前事务,当前事务将会被挂起

Propagation_Nerver:

表示当前方法不应该在事务上下文中,如果当前正有一个事务在运行,就会抛出异常

Propagation_Nested:

如果当前已经存在一个事务,那么该方法会在嵌套事务中运行,嵌套的事务可以独立于当前事务进行单独地提交或回滚,如果当前事务不存在,那么其行为与Propagation_Required一样
外层事务失败的时候,会回滚内层事务所做的动作。但是内层事务失败并不会引起外层事务的回滚。


参考资料

Spring七种事务传播机制
脏读、不可重复读、幻读