spring事务中隔离级别和spring的事务传播机制

简介: Transaction 也就是所谓的事务了,通俗理解就是一件事情。从小,父母就教育我们,做事情要有始有终,不能半途而废。 事务也是这样,不能做一般就不做了,要么做完,要 么就不做。也就是说,事务必须是一个不可分割的整体,就像我们在化学课里学到的原子,原子是构成物质的最小单位。

Transaction 也就是所谓的事务了,通俗理解就是一件事情。从小,父母就教育我们,做事情要有始有终,不能半途而废。 事务也是这样,不能做一般就不做了,要么做完,要 么就不做。也就是说,事务必须是一个不可分割的整体,就像我们在化学课里学到的原子,原子是构成物质的最小单位。于是,人们就归纳出事务的第一个特性:原子性(Atomicity)。我靠,一点都不神秘嘛。

特别是在数据库领域,事务是一个非常重要的概念,除了原子性以外,它还有一个极其重要的特性,那就是:一致性(Consistency)。也就是说,执行完数据库操作后,数据不会被破坏。打个比方,如果从 A 账户转账到 B 账户,不可能因为 A 账户扣了钱,而 B 账户没有加钱吧。如果出现了这类事情,您一定会非常气愤,什么 diao 银行啊!

当我们编写了一条 update 语句,提交到数据库的一刹那间,有可能别人也提交了一条 delete 语句到数据库中。也许我们都是对同一条记录进行操作,可以想象,如果不稍加控制,就会出大麻烦来。我们必须保证数据库操作之间是“隔离”的(线程之间有时 也要做到隔离),彼此之间没有任何干扰。这就是:隔离性(Isolation)。要想真正的做到操作之间完全没有任何干扰是很难的,于是乎,每天上班打酱油的数据库专家们,开始动脑筋了,“我们要制定一个规范,让各个数据库厂商都支持我们的规范!”,这个规范就是:务隔离级别(Transaction Isolation Level)。能定义出这样牛逼的规范真的挺不容易的,其实说白了就四个级别:

  1. READ_UNCOMMITTED
  2. READ_COMMITTED
  3. REPEATABLE_READ
  4. SERIALIZABLE

千万不要去翻译,那只是一个代号而已。从上往下,级别越来越高,并发性越来越差,安全性越来越高,反之则反。

当我们执行一条 insert 语句后,数据库必须要保证有一条数据永久地存放在磁盘中,这个也算事务的一条特性, 它就是:持久性(Durability)

归纳一下,以上一共提到了事务的 4 条特性,把它们的英文单词首字母合起来就是:ACID,这个就是传说中的“事务 ACID 特性”!

真的是非常牛逼的特性啊!这 4 条特性,是事务管理的基石,一定要透彻理解。此外还要明确,这四个家伙当中,谁才是老大?

其实想想也就清楚了:原子性是基础,隔离性是手段,持久性是目的,真正的老大就是一致性。数据不一致了,就相当于“江湖乱套了,流氓戴胸罩”。所以说,这三个小弟都是跟着“一致性”这个老大混,为他全心全意服务。

这四个家伙当中,其实最难理解的反倒不是一致性,而是隔离性。因为它是保证一致性的重要手段,是工具,使用它不能有半点差池,否则后果自负!怪不得数据库 行业专家们都要来研究所谓的事务隔离级别了。其实,定义这四个级别就是为了解决数据在高并发下所产生的问题,那又有哪些问题呢?

  1. Dirty Read(脏读)
  2. Unrepeatable Read(不可重复读)
  3. Phantom Read(幻读)

首先看看“脏读”,看到“脏”这个字,我就想到了恶心、肮脏。数据怎么可能脏呢?其实也就是我们经常说的“垃圾数据”了。比如说,有两个事务,它们在并发执行(也就是竞争)。看看以下这个表格,您一定会明白我在说什么:

 

时间
事务 A(存款)
事务 B(取款)
T1
开始事

T2

开始事务
T3

查询余额(1000 元)
T4

取出 1000 元(余额 0 元)
T5
查询余额(0 元

T6

撤销事务(余额恢复为 1000 元)
T7
存入 500 元(余额 500 元)

T8
提交事务


余额应该为 1500 元才对!请看 T5 时间点,事务 A 此时查询余额为 0 元,这个数据就是脏数据,它是事务 B 造成的,明显事务没有进行隔离,渗过来了,乱套了。

所以脏读这件事情是非常要不得的,一定要解决掉!让事务之间隔离起来才是硬道理。

那第 2 条,不可重复读又怎么解释呢?还是用类似的例子来说明:

 

时间
事务 A(存款)
事务 B(取款)
T1
开始事务

T2

开始事务
T3

查询余额(1000 元)
T4
查询余额(1000 元)

T5

取出 1000 元(余额 0 元)
T6

提交事务
T7
查询余额(0 元)


事务 A 其实除了查询了两次以外,其他什么事情都没有做,结果钱就从 1000 变成 0 了,这就是重复读了。可想而知,这是别人干的,不是我干的。其实这样也是合理的,毕竟事务 B 提交了事务,数据库将结果进行了持久化,所以事务 A 再次读取自然就发生了变化。

这种现象基本上是可以理解的,但在有些变态的场景下却是不允许的。毕竟这种现象也是事务之间没有隔离所造成的,但我们对于这种问题,似乎可以忽略。

最后一条,幻读。我去!Phantom 这个单词不就是“幽灵、鬼魂”吗?刚看到这个单词时,真的把我的小弟弟都给惊呆了。怪不得这里要翻译成“幻读”了,总不能翻译成“幽灵读”、“鬼魂读”吧。其实意义就是鬼在读,不是人在读,或者说搞不清楚为什么,它就变了,很晕,真的很晕。还是用一个示例来说话吧:

时间
事务 A(统计总存款)
事务 B(存款)
T1
开始事务

T2

开始事务
T3
统计总存款(10000 元

T4

存入 100 元
T5

提交事务
T6
统计总存款(10100 元


银行工作人员,每次统计总存款,都看到不一样的结果。不过这也确实也挺正常的,总存款增多了,肯定是这个时候有人在存钱。但是如果银行系统真的这样设计, 那算是玩完了。这同样也是事务没有隔离所造成的,但对于大多数应用系统而言,这似乎也是正常的,可以理解,也是允许的。银行里那些恶心的那些系统,要求非 常严密,统计的时候,甚至会将所有的其他操作给隔离开,这种隔离级别就算非常高了(估计要到 SERIALIZABLE 级别了)。

归纳一下,以上提到了事务并发所引起的跟读取数据有关的问题,各用一句话来描述一下:

  1. 脏读:事务 A 读取了事务 B 未提交的数据,并在这个基础上又做了其他操作。
  2. 不可重复读:事务 A 读取了事务 B 已提交的更改数据。
  3. 幻读:事务 A 读取了事务 B 已提交的新增数据。

第一条是坚决抵制的,后两条在大多数情况下可不作考虑。

这就是为什么必须要有事务隔离级别这个东西了,它就像一面墙一样,隔离不同的事务。看下面这个表格,您就清楚了不同的事务隔离级别能处理怎样的事务并发问题:

 

事务隔离级别 脏读 不可重复读 幻读
READ_UNCOMMITTED
允许 允许 允许
READ_COMMITTED
禁止 允许 允许
REPEATABLE_READ
禁止 禁止 允许
SERIALIZABLE
禁止 禁止 禁止


根据您的实际需求,再参考这张表,最后确定事务隔离级别,应该不再是一件难事了。

JDBC 也提供了这四类事务隔离级别,但默认事务隔离级别对不同数据库产品而言,却是不一样的。我们熟知的 MySQL 数据库的默认事务隔离级别就是 READ_COMMITTED,Oracle、SQL Server、DB2等都有有自己的默认值。我认为 READ_COMMITTED 已经可以解决绝大多数问题了,其他的就具体情况具体分析吧。

若对其他数据库的默认事务隔离级别不太清楚,可以使用以下代码来获取:

?
1
2
DatabaseMetaData meta = DBUtil.getDataSource().getConnection().getMetaData();
int defaultIsolation = meta.getDefaultTransactionIsolation();

提示:在 java.sql.Connection 类中可查看所有的隔离级别。

我们知道 JDBC 只是连接 Java 程序与数据库的桥梁而已,那么数据库又是怎样隔离事务的呢?其实它就是“锁”这个东西。当插入数据时,就锁定表,这叫“锁表”;当更新数据时,就锁定行, 这叫“锁行”。当然这个已经超出了我们今天讨论的范围,所以还是留点空间给我们的 DBA 同学吧,免得他没啥好写的了。

除了 JDBC 给我们提供的事务隔离级别这种解决方案以外,还有哪些解决方案可以完善事务管理功能呢?

不妨看看 Spring 的解决方案吧,其实它是对 JDBC 的一个补充或扩展。它提供了一个非常重要的功能,就是:事务传播行为(Transaction Propagation Behavior)

确实够牛逼的,Spring 一下子就提供了 7 种事务传播行为,这 7 种行为一出现,真的是亮瞎了我的狗眼!

  1. PROPAGATION_REQUIRED
  2. RROPAGATION_REQUIRES_NEW
  3. PROPAGATION_NESTED
  4. PROPAGATION_SUPPORTS
  5. PROPAGATION_NOT_SUPPORTED
  6. PROPAGATION_NEVER
  7. PROPAGATION_MANDATORY

看了 Spring 参考手册之后,更是晕了,这到底是在干嘛?

首先要明确的是,事务是从哪里来?传播到哪里去?答案是,从方法 A 传播到方法 B。Spring 解决的只是方法之间的事务传播,那情况就多了,比如:

  1. 方法 A 有事务,方法 B 也有事务。
  2. 方法 A 有事务,方法 B 没有事务。
  3. 方法 A 没有事务,方法 B 有事务。
  4. 方法 A 没有事务,方法 B 也没有事务。 

这样就是 4 种了,还有 3 种特殊情况。还是用我的 Style 给大家做一个分析吧:

假设事务从方法 A 传播到方法 B,您需要面对方法 B,问自己一个问题:

方法 A 有事务吗?

  1. 如果没有,就新建一个事务;如果有,就加入当前事务。这就是 PROPAGATION_REQUIRED,它也是 Spring 提供的默认事务传播行为,适合绝大多数情况。
  2. 如果没有,就新建一个事务;如果有,就将当前事务挂起。这就是 RROPAGATION_REQUIRES_NEW,意思就是创建了一个新事务,它和原来的事务没有任何关系了。
  3. 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。这就是 PROPAGATION_NESTED,也就是传说中的“嵌套事务”了,所嵌套的子事务与主事务之间是有关联的(当主事务提交或回滚,子事务也会提交或回滚)。
  4. 如果没有,就以非事务方式执行;如果有,就使用当前事务。这就是 PROPAGATION_SUPPORTS,这种方式非常随意,没有就没有,有就有,有点无所谓的态度,反正我是支持你的。
  5. 如果没有,就非事务方式执行;如果有,就将当前事务挂起。这就是 PROPAGATION_NOT_SUPPORTED,这种方式非常强硬,没有就没有,有我也不支持你,把你挂起来,不鸟你。
  6. 如果没有,就非事务方式执行;如果有,就抛出异常。这就是 PROPAGATION_NEVER,这种方式更猛,没有就没有,有了反而报错,确实够牛的,它说:我从不支持事务!
  7. 如果没有,就抛出异常;如果有,就使用当前事务。这就是 PROPAGATION_MANDATORY,这种方式可以说是牛逼中的牛逼了,没有事务直接就报错,确实够狠的,它说:我必须要有事务!

看到我上面这段解释,小伙伴们是否已经感受到,被打通任督二脉的感觉?多读几遍,体会一下,就是您自己的东西了。

需要注意的是 PROPAGATION_NESTED,不要被它的名字所欺骗,Nested(嵌套),所以凡是在类似方法 A 调用方法 B 的时候,在方法 B 上使用了这种事务传播行为,如果您真的那样做了,那您就错了。因为您错误地以为 PROPAGATION_NESTED 就是为方法嵌套调用而准备的,其实默认的 PROPAGATION_REQUIRED 就可以帮助您,做您想要做的事情了。

Spring 给我们带来了事务传播行为,这确实是一个非常强大而又实用的功能。除此以外,也提供了一些小的附加功能,比如:

  1. 事务超时(Transaction Timeout)为了解决事务时间太长,消耗太多的资源,所以故意给事务设置一个最大时常,如果超过了,就回滚事务。
  2. 只读事务(Readonly Transaction)为了忽略那些不需要事务的方法,比如读取数据,这样可以有效地提高一些性能。

最后,推荐大家使用 Spring 的注解式事务配置,而放弃 XML 式事务配置。因为注解实在是太优雅了,当然这一切都取决于您自身的情况了。

在 Spring 配置文件中使用:

?
1
2
3
...
< tx:annotation-driven />
...

在需要事务的方法上使用:

?
1
2
3
4
@Transactional
public void xxx() {
     ...
}

可在 @Transactional 注解中设置:事务隔离级别、事务传播行为、事务超时时间、是否只读事务。

目录
打赏
0
1
0
0
11
分享
相关文章
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
141 2
|
1月前
|
Spring 手动实现Spring底层机制
Spring 第六节 手动实现Spring底层机制 万字详解!
78 31
【SpringFramework】Spring事务
本文简述Spring中数据库及事务相关衍伸知识点。
51 9
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
157 13
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
207 14
Spring高手之路26——全方位掌握事务监听器
本文深入探讨了Spring事务监听器的设计与实现,包括通过TransactionSynchronization接口和@TransactionalEventListener注解实现事务监听器的方法,并通过实例详细展示了如何在事务生命周期的不同阶段执行自定义逻辑,提供了实际应用场景中的最佳实践。
94 2
Spring高手之路26——全方位掌握事务监听器
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
88 1
Spring高手之路24——事务类型及传播行为实战指南
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
168 4
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
117 8

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等