2.Spring 事务

Spring 事务
1.事务类型(以下主要是声明式事务的介绍)
1.1编程式事务:
1.1.1基于底层的APl :
PlatformTransactionManager、
TransactionDefinition 、
TransactionTemplate 等核心接口。
1.1.2需要在代码中手动的管理事务的开启、提交、回滚等操作。

1.2声明式事务(事务注解)
1.2.1实现: AOP 实现,本质是在目标方法执行前后进行拦截。
在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。
eg:
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
意义:
rollbackFor = Exception.class:当发生任何 Exception 及其子类异常时都会回滚事务
propagation = Propagation.REQUIRED:如果当前存在事务则加入该事务,如果不存在则创建新事务

2.事务传播行为详解
2.1spring事务执行框架
2.1.1线程隔离:每个线程有独立的事务上下文,通过ThreadLocal实现
2.1.2同一时刻限制:一个线程在同一时刻只能有一个活动事务
2.1.3事务检查机制:每次调用带@Transactional的方法时,Spring都会检查当前线程是否已存在事务
2.1.4传播行为决定:根据传播行为决定是加入现有事务还是创建新事务

2.2传播行为
在这里插入图片描述

2.3传播行为场景
2.3.1 REQUIRED
订单处理类操作
必须保证订单+库存+支付操作原子性,有事务加入/无事务新建,防部分成功。

2.3.2 REQUIRES_NEW
审计日志/序列号生成
独立事务保存关键操作记录,主事务失败日志仍持久化。

2.3.3 SUPPORTS
报表查询/数据搜索
灵活适配事务环境,有事务读已提交/无事务读最新,避免查询强制开事务。

2.3.4 MANDATORY
金融转账/资金操作
强制事务保护防裸调,无事务直接报错堵死风险。

2.3.5 NOT_SUPPORTED
文件导入/批量处理
挂起事务非阻塞执行,避免长事务锁表。

2.3.6 NEVER
大数据分析/复杂报表
禁止事务污染纯查询,消除性能损耗。

2.3.7 NESTED
批量订单/库存同步
子事务独立回滚,单条失败不影响整体批次。

2.4 以三派来理解

派别核心信条包含的行为特点简述
🤝 协同派能共存,愿共享REQUIRED(默认), SUPPORTS, NESTED倾向于与外部事务协作,共同进退或灵活参与。
🚀 独立派我要自由,不受影响REQUIRES_NEW, NOT_SUPPORTED追求独立执行,会挂起外部事务,避免相互干扰。
⚔️ 约束派立场鲜明,非黑即白MANDATORY, NEVER对当前是否存在事务有强制性要求,不符合则报错。

2.4.1. 协同派 (Team Players)
此派别的行为倾向于与外部事务合作。

  • REQUIRED (默认) - 最温和的协作者

    • 行为:如果当前存在事务,就加入该事务;如果当前没有事务,则新建一个事务。
    • 场景:这是最常用的设置,适用于大多数业务场景,如下单减库存,保证这些操作在同一个事务中,一荣俱荣,一损俱损。
  • SUPPORTS - 随缘的协作者

    • 行为:支持当前事务。如果当前存在事务,就加入该事务;如果当前没有事务,则以非事务方式执行。
    • 场景:适用于查询等操作,如查余额、查资料,既能在事务中执行,也能以非事务方式运行。
  • NESTED - 带“安全点”的协作者

    • 行为:如果当前存在事务,则在其内部创建一个嵌套事务(可视为一个保存点)。嵌套事务可以独立回滚而不影响外部事务,但外部事务回滚会导致嵌套事务回滚。如果当前没有事务,其行为同REQUIRED
    • 场景:适用于复杂业务流程中允许局部失败的场景,如一个订单处理包含多个子步骤,某个子步骤失败不应影响已完成的步骤。

2.4.2 独立派 (Independent Players)
此派别的行为要求独立运行,会挂起已存在的事务。

  • REQUIRES_NEW - 独立王者

    • 行为:无论如何都会启动一个新的事务。如果当前存在事务,则将其挂起。两个事务相互独立。
    • 场景:如日志记录,即使主业务逻辑失败并回滚,日志记录仍应独立提交。
  • NOT_SUPPORTED - 事务绝缘体

    • 行为:以非事务方式执行。如果当前存在事务,则将其挂起。
    • 场景:用于不需要事务控制的纯查询,或者与不支持事务的第三方系统交互,避免事务束缚。

2.4.3约束派 (Rule Enforcers)
此派别的行为对当前环境是否有事务有严格的强制性要求。

  • MANDATORY - 强制必须在事务中

    • 行为:强制要求必须在一个已存在的事务中运行。如果当前存在事务,则加入;如果当前没有事务,则抛出异常。
    • 场景:用于确保某些关键操作(如资金结算)必须被包含在一个更大的事务上下文中,防止被意外非事务调用。
  • NEVER - 强制必须在非事务中

    • 行为:强制要求必须在非事务环境下执行。如果当前存在事务,则抛出异常。
    • 场景:态度强硬,确保方法(如某些纯计算或消息发送)绝不会在事务中执行,避免事务开销或外部系统交互意外受事务影响。

3.事务特性
3.1spring事务只回滚sql
在这里插入图片描述
3.2事务特性
在这里插入图片描述
3.3隔离级别选择策略
READ_COMMITTED:适用于大多数业务场景,性能与一致性平衡。
REPEATABLE_READ:适用于报表统计、数据分析等需要一致性读取的场景。
SERIALIZABLE:适用于金融系统中的关键业务,如账户余额计算。

4.事务嵌套问题
4.1.数据不一致问题
本质原因:外部操作无法回滚
• 数据库事务可回滚,但RPC调用/消息发送/文件写入等操作无法撤销
• 事务回滚时外部操作已产生副作用

典型场景:
用户下单时,系统需要:保存订单、调用支付接口扣款、发送短信通知、检查并预留库存。如果库存检查失败导致事务回滚,此时钱已经被扣了,短信也发了,但订单却被回滚了,造成用户被扣款但没有订单的严重问题。

4.2长事务连接耗尽问题
本质原因:外部操作延长事务时间
• 数据库操作:50ms
• RPC调用:3s
• 事务总时长:3s+
• 100并发 → 连接占用300秒 → 连接池耗尽

典型场景:
处理订单时,保存数据到数据库可能只需要50毫秒,但调用支付接口需要3秒,调用物流接口需要2秒,发送邮件需要1秒。原本50毫秒就能释放的数据库连接,现在要占用6秒多。如果有100个并发订单,就需要占用连接池10分钟,很容易导致连接池耗尽,整个系统瘫痪。

4.2 解决方案1:NOT_SUPPORTED传播行为 - 排除外部操作
核心思路:将外部操作排除在事务外

4.2.1 解决数据不一致
步骤一:先在事务中完成所有必需的数据库操作,包括保存订单、检查库存、预留库存等,确保核心业务数据的一致性。
步骤二:事务提交后,再使用NOT_SUPPORTED传播行为调用外部操作,如支付接口、短信发送等。由于这些操作不在事务中,即使失败也不会影响已经提交的数据库数据。
步骤三:如果外部操作失败,通过补偿机制处理,比如记录失败日志,安排重试,或者更新订单状态为"待处理"等。

4.2.2 解决连接占用
核心原理:将耗时的外部操作从事务中移除,让数据库事务只包含快速的数据库操作,大幅缩短事务持续时间。
具体做法:原本一个6秒的长事务,拆分为一个50毫秒的数据库事务和若干个不占用数据库连接的外部操作。数据库连接在50毫秒后就被释放,外部操作在独立的线程或方法中执行,不占用数据库资源。

4.3 解决方案2:REQUIRES_NEW传播行为 - 优先保护核心数据
核心思路:将核心数据操作放在独立的事务中优先提交

4.3.1 解决数据不一致
步骤一:使用REQUIRES_NEW传播行为创建一个独立事务,在其中保存订单的核心信息,如订单基本数据、库存预留等。这个事务会立即提交,确保核心数据已经安全保存。
步骤二:在主事务中处理外部操作,如调用支付接口、发送通知等。如果这些操作成功,可以更新订单状态为"已完成"。
步骤三:如果外部操作失败,由于核心数据已经在独立事务中提交,不会丢失。此时可以将订单状态更新为"处理失败",后续可以安排重试或人工处理。

4.3.2 解决连接占用
核心原理:将一个长事务拆分为多个短事务,每个短事务只处理特定的操作,快速提交后立即释放连接。
具体做法:比如原本一个处理订单的长事务,可以拆分为:第一个短事务保存订单基础信息,第二个短事务更新支付结果,第三个短事务更新物流信息。每个事务都很短,连接占用时间从原来的6秒缩短到总共几百毫秒。

4.4 解决方案3:异步处理 + 事务分离
核心思路:将所有耗时的外部操作完全异步化

4.4.1 解决数据不一致
步骤一:主方法在事务中快速完成核心数据库操作,如保存订单、预留库存等,然后立即提交事务。
步骤二:发起异步任务处理所有外部操作。异步任务使用NOT_SUPPORTED传播行为,不参与任何事务。
步骤三:异步任务中按顺序处理外部操作,如先调用支付接口,成功后再安排物流,最后发送通知。每个步骤都有相应的补偿机制。
步骤四:异步任务完成后,使用REQUIRES_NEW传播行为在新事务中更新订单的最终状态。

4.4.2 解决连接占用
核心原理:主业务流程的数据库事务只包含必需的数据库操作,所有外部操作都在异步线程中执行,完全不占用主流程的数据库连接。
具体效果:用户请求的响应时间从原来的6秒缩短到50毫秒,数据库连接占用时间也从6秒缩短到50毫秒。外部操作在后台异步执行,不影响用户体验和系统性能。

4.5 方案选择策略
4.5.1 选择NOT_SUPPORTED方案的场景
• 外部操作失败是可以接受的,比如缓存更新、统计信息更新
• 业务流程相对简单,不需要复杂的状态管理
• 希望实现简单,快速解决问题

4.5.2 选择REQUIRES_NEW方案的场景
• 核心数据必须保存,不能因为外部操作失败而丢失
• 业务流程有明确的优先级,比如订单数据比支付通知更重要
• 可以接受中等程度的实现复杂度

4.5.3 选择异步处理方案的场景
• 对用户响应时间要求很高
• 系统并发量大,需要优化性能·
• 对外部操作失败有完善的重试和监控机制
• 可以接受最终一致性,不要求强一致性

4.5.4 组合使用的策略
组合使用多种方案:
• 核心业务数据用REQUIRED保证一致性
• 关键操作日志用REQUIRES_NEW确保记录
• 外部系统调用用NOT_SUPPORTED或异步处理
• 缓存更新等非关键操作用NOT_SUPPORTED

5.事务失效原因
核心原因:
1.围绕声明式事务实现:AOP代理(bean)失效。
2.声明式事务配置以及执行问题。

5.1围绕声明式事务实现:AOP代理(bean)失效
5.1.1@Transactional 应用在非 public 修饰的方法上,例如是 private 修饰 (本质是this自调用)。
private方法,只会在当前对象中的其他方法中调用,也就是会进行对象的自调用,这种情况是用this调用的,并不会走到代理对象,而@Transactional是基于动态代理实现的,所以代理会失效。则事务也会失效

5.1.2 final、static 方法
5.2.1final方法:AOP是通过创建代理对象来实现的,无法对final方法进行子类化和覆盖,所以无法拦截这些方法。
5.2.2static方法:因为这类方法是属于这个类的,并不是对象的,所以无法被AOP。

5.1.3 AOP配置问题
切面顺序: 自定义AOP切面与事务切面的执行顺序可能影响事务行为。
解决方案: 通过@Order注解或实现Ordered接口控制切面执行顺序。

5.2 声明式事务配置以及执行问题。
5.2.1异常被catch捕获导致@Transactional失效
因为异常被捕获,所以就没办法基于异常进行rolback了,所以事务会失效
public class MyService{
@Transactional
public void dosomething(){
try{
doInternal();
}catch(Exception e){
logger.error(e);
}
}
}

5.2.2 @Transactional 注解属性 rollbackFor 设置
public class MyService{
@Transactional(rollbackFor = RuntimeException.class)
private void doInternal(){
System.out.println(“Doing internal work…”);
}
}

如果发生 非RuntimeException的异常,则事务不会回滚,即导致事务失效。
所以需要指定为(rollbackFor =Exception.class)

5.2.3 事务管理器配置错误
多数据源场景: 没有正确配置对应的事务管理器,导致事务不生效。
解决方案: 为每个数据源配置对应的事务管理器,并在@Transactional中指定使用哪个。

5.2.4 事务中用了多线程
@Transactional 的事务管理使用的是 ThreadLocal 机制来存储事务上下文,而 ThreadLocal 变量是线程隔离的,即每个线程都有自己的事务上下文副本。因此,在多线程环境下,Spring 的声明式事务会失效,即新线程中的操作不会被包含在原有的事务中。

6.补充事务注意点
6.1缓存一致性问题
问题场景: 事务中更新数据库后立即更新缓存,如果事务回滚,缓存中就是脏数据。
解决方案:
使用NOT_SUPPORTED在事务外更新缓存
采用缓存失效策略而非更新策略
使用分布式缓存的事务特性

6.2分布式事务问题
跨服务调用: 当业务流程跨越多个微服务时,本地事务无法保证整体一致性。
解决思路:
最终一致性:通过事件驱动、状态机等方式
分布式事务框架:如Seata、TCC等
补偿机制:Saga模式

6.3服务调用中的事务传播
微服务之间通过HTTP调用时,事务上下文不会自动传播,需要通过其他机制(如消息队列、事件总线)来保证数据一致性。

6.4 超时时间配置策略
默认问题: Spring事务默认超时时间是-1(永不超时),在生产环境中容易导致长事务阻塞系统。
设置原则:
简单操作:5-10秒
复杂业务流程:30-60秒
批量处理:根据数据量动态设置
超时处理: 超时后事务会自动回滚,需要有相应的异常处理机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值