阿粉带你搞懂事务,事务隔离级别,事务传播行为之间的关系

大家好,今天我鸭血粉丝必须给大家宣布一个好消息:在昨晚查阅了大量资料之后,我终于搞懂了事务,事务隔离级别,以及事务传播行为三者之间的关系!

但是转头一想,独乐乐不如众乐乐,所以便有了今天这篇文章,希望大伙看完也能够理解这三者之间的关系。

事务

首先从事务这个概念开始谈,事务最开始是数据库中的概念,它把一系列的操作统一为一个整体,这一系列的操作要么同时成功,要么同时失败。

一个事务基本的操作是这样子的:

  1. 开始事务
  2. 如果发生了错误,进行回滚
  3. 如果没有发生错误,则提交事务

看到这里你可能会想,为啥会有事务???

你可以这么想:在我们处理简单业务的时候,比如说一条插入数据的操作,只会得到两个结果,要么插入成功,要么插入失败,这对应到代码逻辑上是很简单的。 我们称这样的操作具有原子性。

但是我们的业务往往不会只有插入一条数据那么简单,可能用户点击一个按钮后,我们需要插入一条数据和删除一条数据。由于每个操作都有可能成功和失败,这个时候我们就有了2^2=4种情况,这下编程起来就麻烦了。

为了方便编程(也为了符合实际业务逻辑),我们引入开头所述的事务机制,把两个操作放在一个事务里面,使这两个操作具备原子性,这样一来业务处理起来就方便多了。

事务隔离级别

说完了事务,接下来我们谈事务隔离级别。

要知道在数据库中,难免会出现多个事务同时操作数据的情况,这时数据库设置的事务隔离级别不同,会出现不同的数据操作结果,经典的脏读与幻读也诞生于此。

在逐个讲解事务隔离级别之前,阿粉先给大家看个表格,如果你没了解过事务隔离级别,可以先根据这个表格观察一下规律,然后再看后面的例子

事务隔离级别 脏读 不可重复读 幻读
read uncommitted 读未提交
read committed 读提交 不会
repeatable read 可重复读 不会 不会
serializable 串行化 不会 不会 不会

从表格中可以看到事务隔离级别越高,产生的问题越少,但是相应的性能是会降低的,下面通过几个例子分别阐述不同事务隔离级别下发生的问题:

读未提交

当事务隔离设置为读未提交时,最容易产生的问题是脏读。读未提交指的是当前事务可以读到其他事务未提交的数据。

比如说阿粉的老板要给阿粉发工资,本来要发6000块但不小心手抖多打了一个0变成60000,这时候阿粉去商场消费一查余额发现多了60000,激动万分,拼命扫货。庆幸的是老板发工资的事务还没提交,他立马回滚事务,重新发放6000块工资。

这个时候阿粉读到的余额就是脏数据,产生的原因是读取了其他事务未提交的数据。

读提交

为了解决上面提到的脏读问题,我们可以把事务隔离级别设置为读提交,他能保证当前事务只能读到其他事务已经提交的数据。但是读提交会面临一个新问题:不可重复读

比如说阿粉到商店消费,买单的时候(开启当前事务),系统检测到卡里有6000元。这个时候阿粉的女朋友(暂时给阿粉安排一个)在别的地方用阿粉的卡刷了5000元,当阿粉准备扣费的时候,再查询余额,发现只剩下了1000元(这个查询发生在女朋友消费之后)

像这样,在同一个事务中,阿粉的余额在不同的时候读取的值不一样,这就是不可重复读问题。想要解决这个问题,需要把事务隔离级别设置为可重复读

可重复读

在可重复读的情况下,当前事务会禁止其他事务对正在操作的数据进行更新,这样一来,女朋友消费的事务就要等到阿粉账号扣费结束后才能进行,从此解决了不可重复读问题。

但是可重复读级别下还可能发生一个问题叫幻读,举例如下:阿粉今天在外消费了1000元,女朋友查看阿粉今天的消费记录(开启事务),发现一共是1000元。这个时候阿粉又消费了1000元(女朋友的事务还在进行中),当女朋友打印阿粉今天的消费记录时,发现莫名其妙地变成了2000元,而且多了一条消费记录。

像这种当前事务在操作的过程中,由于别的事务增加或删除数据,导致当前事务操作的数据突然变多或变少的情况,就叫幻读。想要解决幻读,需要把事务隔离级别升级为串行化

串行化

当事务隔离级别为串行化时,所有事务都是串行执行的,对应上面的例子:女朋友在查看当天消费记录时,阿粉是不能消费的。这么一来事务并发带来的问题都能解决,但是效率很低。

扩展

在了解了事务隔离级别之后,阿粉想提醒大家:在常用的数据库中Orcale默认的事务隔离级别是读提交,而Mysql默认的是可重复读。在Mysql的InnoDB引擎中,虽然事务隔离级别是可重复读,但是也可以解决幻读问题,背后的原理是在数据行之间添加间隙锁,防止数据的插入与删除。具体选择那一种事务隔离级别,要看具体的业务需要。

事务传播行为

相信看到这里大家对事务和事务隔离级别已经有了初步的认识,再坚持把事务传播行为这小段看完就能串联起它们的关系了!

事务的传播行为从字面上也是挺好理解的:想要发生传播就一定要有两个以上的物体,而这里指的是两个方法都要在事务中进行,当一个事务方法A调用另一个事务方法B时,另一个事务方法B该如何运行。

Spring一共定义了7种事务传播行为(事务方法B该如何运行):

传播行为 含义
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中(这是最常见的选择,也是spring的默认事务传播行为)
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作

平时我们最常用的是 PROPAGATION_REQUIRED,这也是spring的默认事务传播行为,理解了它就能按理推导其他的事务传播行为。

比如说当前我们有如下代码:

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
}
  1. 假设我们执行methodA,由于当前还没有事务,于是就新创建一个事务。
  2. 当在methodA中调用methodB的时候,由于methodA已经存在于事务中,于是methodB便无需新创建一个事务,直接加入到methodA的事务中即可。

总结

最后阿粉尝试通过三句话分别总结事务,事务隔离级别和事务传播行为:

  1. 事务能够让一系列不同的操作具有原子性。(当然事务具备ACID四大特性,本文在初步介绍时强调的是原子性)
  2. 事务隔离级别定义了事务并发操作时的访问规则。
  3. 事务传播行为定义了事务方法在执行时该怎么运用事务。

参考文章:
事务隔离级别:https://www.cnblogs.com/ubuntu1/p/8999403.html
事务传播行为:https://blog.csdn.net/weixin_39625809/article/details/80707695
事务传播行为:https://www.cnblogs.com/softidea/p/5962612.html

Java Geek Tech wechat
欢迎订阅 Java 极客技术,这里分享关于 Java 的一切。