Spring事务与分布式事务

简介: 这篇文档介绍了事务的概念和数据库事务的ACID特性:原子性、一致性、隔离性和持久性。在并发环境下,事务可能出现更新丢失、脏读和不可重复读等问题,这些问题通过设置事务隔离级别(如读未提交、读已提交、可重复读和序列化)来解决。Spring事务传播行为有七种模式,影响嵌套事务的执行方式。`@Transactional`注解用于管理事务,其属性包括传播行为、隔离级别、超时和只读等。最后提到了分布式事务,分为跨库和跨服务两种情况,跨服务的分布式事务通常通过最终一致性策略,如消息队列实现。

一、事务的具体定义

事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败(出现异常),都将导致整个事务的回滚。简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制。

明白上面的这几句话,ACID就不用看了,ACID就是对这句话的一个解释。

   原子性(Atomicity): 一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。

   一致性(Consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态。在事务开始前后,数据库的完整性约束没有被破坏。例如违反了唯一性,必须撤销事务,返回初始状态。

   隔离性(Isolation): 每个读写事务的对象对其他事务的操作对象能相互分离,即:事务提交前的数据对其他事务是不可见的,通常内部加锁实现。不同的隔离级别加不同的锁。

   持久性(Durability): 一旦事务提交,则其所做的修改会永久保存到数据库。

二、并发环境下的数据库事务

2.1 事务并发执行会出现的问题

我们先来看一下事务并发,数据库可能会出现的问题:

   更新丢失(问题严重)

   当有两个并发执行的事务,更新同一行数据,那么有可能一个操作会把另一个操作的更新数据覆盖掉。

   脏读 (问题严重)

   一个事务读到另一个尚未提交的事务中的数据,即读到了事务的处理过程中的数据,而不是结果数据。 该数据可能会被回滚从而失效。 如果第一个事务拿着失效的数据去处理那就发生错误了。

   不可重复读 (一般来说可以接受,比如你交话费,交完就查看可能没到账,过2分钟再查就到账了)

   不可重复读的含义:一个事务对同一行数据读了两次,却得到了不同的结果。它具体分为如下两种情况:

   虚读:在事务1两次读取同一记录的过程中,事务2对该记录进行了修改,从而事务1第二次读到了不一样的记录。

   幻读:事务1在两次查询的过程中,事务2对该表进行了插入、删除操作,从而事务1第二次查询的结果数量发生了变化。

   不可重复读 与 脏读 的区别?

   脏读读到的是尚未提交的数据,而不可重复读读到的是已经提交的数据,只不过在两次读的过程中数据被另一个事务改过了。

2.3 如何解决并发过程中事务问题(事务隔离)

数据库一共有如下四种隔离级别:

   Read uncommitted 读未提交

   在该级别下,一个事务对一行数据修改的过程中,不允许另一个事务对该行数据进行修改,但允许另一个事务对该行数据读。

   因此本级别下,不会出现更新丢失,但会出现脏读、不可重复读。

   Read committed 读提交 (oracle、sqlserver默认的隔离级别)

   在该级别下,未提交的写事务不允许其他事务访问该行,因此不会出现脏读;但是读取数据的事务允许其他事务的访问该行数据,因此会出现不可重复读的情况。

   Repeatable read 重复读 (mysql的默认隔离级别)

   简单说就是:一个事务开始读或写数据时,不允许其他事务对该数据进行修改。在该级别下,读事务禁止写事务,但允许读事务,因此不会出现同一事务两次读到不同的数据的情况(不可重复读),且写事务禁止其他一切事务。这个级别无法解决幻读问题。

   Serializable 序列化

   该级别要求所有事务都必须串行执行,因此能避免一切因并发引起的问题,但效率很低。

隔离级别 出现更新丢失问题 出现脏读问题 出现虚读问题 出现幻读问题 实现方式

Read uncommitted 读未提交 No Yes Yes Yes 排他写锁

Read committed 读提交 No No Yes Yes 瞬间共享读锁与排他写锁

Repeatable read 重复读 No No No Yes 共享读锁与排他写锁

Serializable 序列化 No No No No  

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,应该由应用程序员采用悲观锁或乐观锁来控制。

三、Spring事务传播行为

举例说明

事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

用伪代码说明:

   ServiceA {

            @Transactional(Propagation=XXX)

            void methodA() {

                //其他持久层操作数据库

                ServiceB.methodB();

            }

   }

       

   ServiceB {

            @Transactional(Propagation=YYY)

            void methodB() {

               //持久层操作数据库

            }

   }

代码中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transactional(Propagation=YYY)设置决定。

Spring中七种事务传播行为

事务传播行为类型 说明

PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

定义非常简单,也很好理解,下面我们就进入代码测试部分,验证我们的理解是否正确。

   回答一个问题:当一个Service函数里面既使用Mybatis Mapper,又使用JdbcTemplate操作同一个数据库,能保证二者操作的整体事务么? 答案是可以的,因为事务控制器是在Spring的层面控制的,与持久层框架无关。

四、Spring @Transactional 注解

新建的Spring Boot项目中,一般都会引用spring-boot-starter或者spring-boot-starter-web,而这两个起步依赖中都已经包含了对于spring-boot-starter-jdbc或spring-boot-starter-data-jpa的依赖。 当我们使用了这两个依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。

所以我们不需要任何额外配置就可以用@Transactional注解进行事务的管理。在spring框架内实现多个数据库持久层操作的事务,我们只需要在方法或类添加@Transactional注解即可。@Transactional注解只能应用到public可见度的方法上,可以被应用于接口定义和接口方法,方法会覆盖类上面声明的事务。

   @Transactional

   public int xxx(){

       // 增删改持久层操作一

       // 增删改持久层操作二

       // ……

   }

 

当多个持久层操作在同一个Service层方法上时,能保证多个持久层操作要么都成功,要么都失败。

属性名 说明

value 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。

propagation 事务的传播行为,默认值为 REQUIRED。

isolation 事务的隔离度,默认值采用 DEFAULT。

timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。

no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。

五、分布式事务

笔者自己将分布式事务分为两种:跨服务的分布式事务,跨库的分布式事务。

5.1.跨库的分布式事务

跨库的分布式事务:一个服务层函数,需要同时操作两个数据库。我们之前给大家讲的例子都是这一种,实际上总的思路:就是有一个“事务管理器”对象统一管理多个数据源事务的提交与回滚。事务管理器协调多数据源进行两段式提交。

为了大家方便理解:我以小故事方式给大家讲一下两段式提交:

   背景:以缉毒警察抓捕专案毒贩为背景,目前3位毒贩A、B、C分别住在不同的住址,目前要实施抓捕。将缉毒大队分成三个组,组A、组B、组C分别针对毒贩A、B、C,三个小组统一由“缉毒大队长”协调指挥。

       三名毒贩住在不同的住址,体现的是“分布式”,3个数据库

       “缉毒大队长”代表的是“事务管理器”TransctionManager,负责抓捕这个事务的协调指挥工作。

       三个抓捕小组,代表的是XAResourceManager,是XA/JTA两阶段提交规范的单一资源操作的执行者。

   抓捕的要求是:把三名毒贩同时抓获,不能先抓A,如果A抓捕失败打草惊蛇,可能给B、C报信。要么就全抓到,要么就一个也别抓,免得打草惊蛇。

       抓捕的要求和我们对于“分布式”事务的要求是一样的,多数据库操作要么都成功,要么都失败。

   抓捕的步骤:

       第一步:三个小组分别靠近毒贩A、B、C的住址,然后等待“缉毒大队长”协调指挥。“缉毒大队长”询问A小组是否完成准备抓捕工作,A小组回复:准备完毕。以此类推,“缉毒大队长”询问B、C两个抓捕小组,这三个组都准备完成了,并且没有异常情况发生,第一阶段工作完毕。即:两阶段提交的第一阶段:预提交。

       如果任何一个小组发现异常,整个行动计划立刻取消。三个抓捕小组同时收队,这个可以认为是数据库事务回滚。

       第二步:三个小组已经全部准备好了,“缉毒大队长”下命令:“抓捕”。三个抓捕小组同时行动,分别抓捕三名毒贩。确保全部落网,一个也跑不掉。这就好比事务两阶段提交的第二阶段:整体提交。

5.2.跨服务的分布式事务

跨服务分布式事务: 也就是说我在做一个服务A的时候,需要通过HTTP网络请求调用多个其他服务,有可能第一个服务B成功了,第二个服务C执行失败了。我们期望的结果是:服务B和服务C都成功。这种分布式单纯的依靠数据库层面就很难解决了。

这种情况一般都是通过最终一致性的方式解决。比如:通过MQ消息队列,给服务B发消息,服务B执行,然后真的做持久化操作数据入库了。

给服务C发消息,如果服务C执行失败,这个消息就会存在MQ里面,依照一定的策略还会发给服务C,直到服务C成功为止。这种策略被叫做“ Exactly-once”,精确的保证成功一次并且只成功一次。这样保障操作结果的最终一致性。


相关文章
|
4月前
|
人工智能 Java Nacos
基于 Spring AI Alibaba + Nacos 的分布式 Multi-Agent 构建指南
本文将针对 Spring AI Alibaba + Nacos 的分布式多智能体构建方案展开介绍,同时结合 Demo 说明快速开发方法与实际效果。
3525 75
|
5月前
|
存储 安全 Java
管理 Spring 微服务中的分布式会话
在微服务架构中,管理分布式会话是确保用户体验一致性和系统可扩展性的关键挑战。本文探讨了在 Spring 框架下实现分布式会话管理的多种方法,包括集中式会话存储和客户端会话存储(如 Cookie),并分析了它们的优缺点。同时,文章还涵盖了与分布式会话相关的安全考虑,如数据加密、令牌验证、安全 Cookie 政策以及服务间身份验证。此外,文中强调了分布式会话在提升系统可扩展性、增强可用性、实现数据一致性及优化资源利用方面的显著优势。通过合理选择会话管理策略,结合 Spring 提供的强大工具,开发人员可以在保证系统鲁棒性的同时,提供无缝的用户体验。
109 0
|
5月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
6月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
1065 3
|
4月前
|
负载均衡 Java API
《深入理解Spring》Spring Cloud 构建分布式系统的微服务全家桶
Spring Cloud为微服务架构提供一站式解决方案,涵盖服务注册、配置管理、负载均衡、熔断限流等核心功能,助力开发者构建高可用、易扩展的分布式系统,并持续向云原生演进。
|
11月前
|
Java Spring
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
|
6月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
11月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
293 0
|
11月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
1459 0
|
11月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
287 0