【Spring深入】一、事务

1、Spring事务

1.1 事务开启类型

  • REQUIRED (默认) 如果已存在事务,则加入该事务,否则新建一个事务
  • REQUIRED_NEW 每次都是新建一个事务,上一个事务挂起
  • SUPPORTS 支持当前事务,如果没有则不创建事务
  • NOT_SUPPORTED 以非事务的方式执行,如果当前存在事务,则挂起
  • MANDATORY 必须在一个已有事务中运行,否则抛异常。
  • NEVER 不能在事务中运行,否则抛异常。
  • NESTED 如果当前存在事务,则在嵌套事务(SavePoint,可回滚子事务,不影响主事务)内执行;否则行为类似 REQUIRED。

1.2 事务原理

  • 基于AOP,通过代理模式,JDK代理或者CGLIB代理
  • 通过拦截器开启事务,提交或者回滚
  • 通过try catch 方式,在catch块中回滚事务
  • 通过ThreadLocal记录当前线程的事务,当前线程是否开启事务,当前线程持有的数据源对应的数据库连接

1.3 事务失效

  • 在一个未开启事务的方法中调用了本对象中一个开启事务的方法,不会走代理
  • 捕获异常,导致无法回滚
  • 在非public的方法上使用@Transactional
  • 抛出异常不匹配@Transactional设定的异常
  • 在方法中使用异步操作

2、分布式事务

2.1 分布式事务的种类

  • 2PC,两阶段提交,其实就是利用本地事务,对修改行开启事务,然后执行完了,协调者向所有参与者发送提交指令,强一致性,但是性能很差,如果哪一个方法有异常,则统一回滚,如果协调者有问题,就会导致事务一直挂起
  • 3PC,相对于2PC增加了预校验,看能不能执行该操作,但是和2PC一样,同样开启本地事务,强一致性,但是性能很差
  • TCC try, confirm, concel,通过预先执行操作,比如转账操作,进行一些不被用户看到的数据库表变更,好处是,所有的操作都是直接执行提交,不会有锁竞争,所以高性能,而且也满足最终一致性,缺点是需要自己实现try confirm concel操作,而且会出现悬挂,空回滚问题,confirm超时问题
  • Saga 长事务模式,每一步的变更都有对应的回退操作,不管哪一步失败,都从失败的地方逐步回退,但是问题是变更状态会被用户直接看到,而且会存在无法回滚的情况,最终一致性
  • 本地消息表模式/MQ模式,在完成某一步操作后,同步插入一条数据到本地消息表,然后通过MQ的方式进行处理,最终一致性
  • seata的AT模式,在每个数据库设置一个属于seata的undo log表,用于回滚真提交事务之后的操作,只能用于数据库,最终一致性
  • seata的XA模式,强一致性,和2PC类似

2.2 TCC的问题

  • Seata 等 TCC 框架内部已经通过 xid + 分支 ID 的方式,在 TC 侧做了幂等控制,但业务开发者依然需要保证自己 Confirm/Cancel 方法的业务逻辑是幂等的,这是双重保险。
  • 每个服务必须记录阶段的状态!
    在方法中,先插入一条事务记录到本地数据库(如 tcc_transaction 表),通过状态来进行幂等处理
问题 原因 解决方案
空回滚 CancelTry 之前到达 Cancel 时检查 Try 是否执行过,没执行过就跳过。
悬挂 Cancel 先执行成功,Try 后到达并执行 Try 时检查是否已被 Cancel,如果已被取消就拒绝执行。
Confirm/Cance 超时 网络问题导致 TM 不确定操作是否成功 确保 ConfirmCancel 方法是幂等的,允许被重复调用。

2.3 Saga和MQ如何做到有迹可循

特性 Saga (Orchestration) MQ (本地消息表)
"迹"的载体 中心化的 Saga 状态机实例表 (saga_id, current_state, history) 去中心化的本地消息表 (message_id, content, status)
追踪方式 通过 saga_id 可以完整还原整个长事务的执行路径。 通过 message_id 只能知道单个消息的生产和消费状态,跨服务的完整链路需要额外的链路追踪(如 SkyWalking)。
协调者 有明确的中心化编排器,负责驱动流程和补偿。 无中心协调者,靠消息的发布/订阅自然驱动。
适用场景 复杂的、多步骤的、需要精确补偿的业务流程(如订单创建、机票预订)。 简单的、点对点的、事件驱动的最终一致性(如用户注册后发欢迎邮件、积分变更)。
  • Saga每次执行新的长事务,会生成一个唯一的saga_id,并且插入到数据库中
  • 同一个长事务,执行到不同阶段,对应的记录也会变更状态
问题 答案
记录执行进度是 INSERT 还是 UPDATE? 首次启动时 INSERT 一条实例记录,后续所有进度更新都是 UPDATE 这条记录。
"执行到哪一步"的状态是用户自定义的吗? 状态的名称(如 "扣库存")由用户在状态机定义文件中自定义,但状态的流转和持久化由框架自动管理。

2.4 分布式事务的要求

  • CAP
  • C considency 一致性
  • A abilty 高可用
  • P Partition 网络分区
  • 其中P是必定会有问题的,所以只能满足CA
分布式事务模式 一致性 © 可用性 (A) 网络分区容忍 § CAP 归属 说明
2PC / 3PC / XA ✅ 强一致性 ❌ 低可用性 CP 协调者或任一参与者宕机/网络分区,整个事务会阻塞挂起,无法对外提供服务(不可用)。但一旦成功,数据绝对一致。
TCC ⚠️ 最终一致性 ✅ 高可用性 AP Try 阶段成功后,资源已被预留,即使后续 Confirm/Cancel 因网络问题失败,系统也是可用的(用户能看到"处理中"状态)。通过后台任务重试,最终能达到一致。牺牲了强一致性,换取了可用性。
Saga ⚠️ 最终一致性 ✅ 高可用性 AP 每个子事务都是独立提交的。即使中间某步因网络问题失败,前面的步骤已经生效(用户可见),系统依然可用。通过执行补偿事务来最终达到一致。典型的 AP 系统。
本地消息表 / MQ 事务消息 ⚠️ 最终一致性 ✅ 高可用性 AP 生产者发消息和本地事务在一个库,保证了原子性。消费者可能因网络问题暂时收不到消息,但消息队列会重试,最终会被消费。整个链路是最终一致且高可用的。
Seata AT ⚠️ 最终一致性 ✅ 高可用性 AP 虽然看起来像本地事务,但它依赖 TC(协调者)。如果 TC 宕机,新的全局事务无法开启,但已经提交的本地事务不受影响,数据库依然可用。回滚依赖异步的 undo_log 清理。设计目标是高性能和高可用,接受最终一致。
相关推荐
Mahir082 小时前
Spring 事务深度解析:核心原理与 12 种事务失效场景全解
java·spring·面试·事务失效
摇滚侠2 小时前
SpringCloud 面试题 真正的 offer 偏方 Java 基础 Java 高级
java·spring·spring cloud
敖正炀3 小时前
Spring 设计哲学再探:约定优于配置、误用与反模式
spring boot·spring
勿忘,瞬间4 小时前
Spring日志
java·spring boot·spring
多敲代码防脱发5 小时前
Spring进阶(Aware接口)
java·后端·spring
未若君雅裁5 小时前
SpringMVC 执行流程详解
java·spring boot·spring·状态模式
未若君雅裁14 小时前
Spring AOP、日志切面与声明式事务原理
java·后端·spring
椰猫子1 天前
SpringBoot(简介、基础配置、整合第三方技术)
java·spring boot·spring
Ting-yu1 天前
Spring AI Alibaba零基础速成(3) ---- ChatClient使用
java·spring·spring cloud·spring ai