恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Spring事务全解:传播行为、失效场景与分布式方案

  • 首页
  • 资讯中心
  • /
  • Spring事务全解:传播行为、失效场景与分布式方案

相关资讯

TensorFlow Lite全链路集成实战:模型转换、量化到移动端部署优化 2026/10/14 23:34:48
从OCR工具到流程治理:文档识别转换工作台设计要点 2026/10/14 23:34:48
MySQL远程连接报错2013?排查网络和服务端配置全指南 2026/10/14 23:34:48

最新资讯

把剪辑写成代码:用Opus 5.5重新定义视频工作流
Java 转 Rust 异步篇:3 个任务从 3.015s 到 1.006s,CompletableFuture/虚拟线程 vs tokio 一次讲透(附真实报错)
Java高并发编程核心原理,初学者如何一周速通?
让智能体在“历史回放”中做梦:破解递归自我改进的元探索困境——读马里兰大学郑童NICE学术前沿分享
基于Spring Boot的毕业旅行一站式定制平台核心设计
Python数据采集分析可视化:疫情数据全链路实战

今日推荐

AI视频生成的时间一致性难题与时空建模演进
基于bbox的PDF文档解析:多栏重排与水印过滤实战
探矿数据进RAG前的清洗全攻略:TXT、Word、PDF、网页四类文档处理实践

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Spring事务全解:传播行为、失效场景与分布式方案

发布时间:2026/10/14 23:34:48
Spring事务全解:传播行为、失效场景与分布式方案 接手项目的第一天我就被同事喊过去看一个“神奇”的现象代码里明明加了Transactional方法执行到一半抛了异常数据却照常写进了数据库。排查了半天发现事务根本没生效——这种事在 Spring 项目里太常见了几乎每来一批新人就要踩一遍。Spring 事务Transaction可以说是后端开发里“阈值最低、坑位最多”的知识点之一你不需要懂它也能写 CRUD但一旦涉及多表写入、资金流水、订单状态变更事务搞不明白线上就是事故现场。这篇内容写给正在用 Spring 写业务、但没系统整理过事务体系的开发者。我会把事务的核心概念、传播行为、Transactional的底层机制、失效场景、编程式事务以及分布式场景的妥协方案串成一条线尽量讲清楚“为什么”而不是只给结论。内容不算短耐心看完应该能帮你少踩几个真金白银的坑。1. 事务到底在解决什么问题从一条 SQL 讲起1.1 事务的本质就是“要么全做要么全不做”先把最底层的概念说透。数据库事务的本质是一组 SQL 操作被当成一个不可分割的单元要么全部成功提交commit要么全部回滚rollback不允许出现“中间状态”残留在数据库里。这个设计背后是 ACID 四个特性原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Durability。很多人背过 ACID 四个英文单词但真正落到业务里感触最深的是“原子性”和“隔离性”。原子性保证的是你在一个事务里执行了 5 条 SQL第 4 条失败了前 3 条不管跑得多成功都得一起撤销。隔离性保证的是多个事务并发执行时彼此不能读到对方的“半成品”数据。拿一个最典型的场景来说用户下单扣库存// 伪代码 1. 扣减库存UPDATE product SET stock stock - 1 WHERE id 123 AND stock 0; 2. 生成订单INSERT INTO order (user_id, product_id, quantity) VALUES (1, 123, 1); 3. 扣减余额UPDATE account SET balance balance - 100 WHERE user_id 1;如果这三条 SQL 不在同一个事务里第 2 步执行完后第 3 步出了异常用户会发现库存扣了、订单有了钱却没扣。这就是典型的“数据不一致”。而有了事务第 3 步一旦失败前两步全部回滚用户看到的最终结果就是“下单失败什么都没发生”。1.2 没有 Spring 的时候事务是怎么写的Java 开发者早期接触 JDBC 的时候写事务是这样的Connection conn dataSource.getConnection(); try { conn.setAutoCommit(false); // 执行 SQL 1 // 执行 SQL 2 // 执行 SQL 3 conn.commit(); } catch (Exception e) { conn.rollback(); } finally { conn.setAutoCommit(true); conn.close(); }这套写法的问题很明显每个需要事务的方法都要重复写getConnection、setAutoCommit(false)、commit/rollback、finally close这一堆模板代码。更麻烦的是事务是依托Connection存在的如果你在一个方法里调了另一个方法另一个方法又开了新的Connection那事务边界就乱了根本控制不住。Spring 事务要解决的核心痛点就是把这套模板化的过程管理收编成统一机制让你在代码里只需要声明“这段逻辑需要事务”剩下的提交、回滚、连接绑定、异常判定全部交给框架处理。这也是Transactional这个注解存在的意义——它的本质是一个声明式事务的开关你只需要告诉 Spring“这里要开事务”Spring 在运行时帮你把 JDBC 那套模板代码全部包好。1.3 Spring 事务的两条路线声明式与编程式Spring 事务体系里实际开发中你能碰到的主要有两种用法。第一种是声明式事务依靠Transactional注解声明在方法上或类上由 Spring AOP 在方法执行前开启事务、方法执行后根据异常情况决定提交还是回滚。第二种是编程式事务使用TransactionTemplate或者直接PlatformTransactionManager在代码里手动控制事务边界。声明式事务是绝大多数业务开发的首选因为代码侵入性最低——加一个注解就完事。但它的缺点也很明显事务边界是隐含的一旦方法被内部调用自调用或者异常被吞掉事务可能静默失效排查起来特别隐蔽。编程式事务则正好相反它把事务的开启、提交、回滚显式地写在代码里逻辑一目了然但代码侵入性高适合事务边界非常特殊、或者需要精细控制回滚点的场景。我个人的建议是常规业务方法优先用Transactional但心里必须清楚它的生效前提遇到一个方法里有多个独立数据操作且需要不同回滚策略的直接上TransactionTemplate别硬啃注解。2. 事务传播行为七个选项背后的真实取舍2.1 传播行为到底解决什么问题传播行为Propagation是 Spring 事务里最容易被忽略、但实际影响最大的概念之一。它的本质是当你在一个已经开启事务的方法内部又调用了另一个带事务注解的方法这个新方法应该怎么处理事务是加入已有事务还是挂起已有事务、新开一个还是直接不参与事务这个问题的典型场景就是“资金余额更新”与“操作流水记录”之间的冲突。例如主业务方法里扣了用户余额事务尚未提交此时同事务内调用了saveLog()记录一条流水。如果这条流水因为某些原因失败了按照业务要求到底是整个扣款回滚还是流水失败不影响扣款这两个要求对应的传播行为完全不同。2.2 七种传播行为逐个说Spring 定义了七种传播行为用Propagation枚举表示Transactional的propagation属性就是干这个的。一个一个来看。REQUIRED是默认值。意思是如果当前存在事务就加入当前事务如果当前没有事务就新建一个事务。这是使用频率最高的传播行为适合绝大多数普通业务方法。它保证的是“整个调用链共享同一个事务”任何一个环节失败整体全部回滚。SUPPORTS当前存在事务则加入不存在则以非事务方式执行。这个适合那些“可事务可不事务”的辅助方法比如只读查询、日志记录前加个事务判断。实际使用中比较少因为大部分方法要么明确要事务要么明确不要。MANDATORY要求当前必须存在事务否则直接抛异常。这个传播行为适合做“校验类”操作比如一个内部方法只允许在外部事务中调用如果被单独拉出来执行了说明调用方代码逻辑有问题直接报错提醒。强制忽略掉具体的技术细节就是“没开事务别来调我”。REQUIRES_NEW挂起当前事务新开启一个独立事务。这是第二个高频使用的传播行为。它的核心特点是新事务的提交回滚与外部事务完全隔离互不影响。外部事务回滚了内部已完成提交的新事务不会跟着回滚内部事务失败了只会回滚自身外部的数据操作不受影响。典型的业务场景就是“异步通知记录、操作日志、工作流引擎的流程推进”。NOT_SUPPORTED如果当前存在事务挂起当前事务以非事务方式执行。这个适合某些特殊的、不希望占用数据库连接的事务场景比如把大文件解析、报文发送等耗时长但不需要事务的活与外部事务隔离开。NEVER如果当前存在事务直接抛异常。要求调用环境绝对不允许有事务适合某些需要在无事务状态下才能正确执行的内部逻辑。实际开发中很少用到。NESTED嵌套事务。这个比较特殊它使用数据库的保存点Savepoint机制实现“局部回滚”。如果外部事务存在NESTED会在当前事务内创建一个保存点内部方法抛异常时可以只回滚到保存点而不影响外部事务已执行的其他操作如果外部事务不存在它的行为和REQUIRED一样直接新建事务。2.3 传播行为选型实际业务中怎么定七种行为看着复杂但落到实际业务里90% 的场景只用三个就够REQUIRED、REQUIRES_NEW、NESTED。REQUIRED用于默认场景一条业务链路的多个数据操作要绑在一起要么全成功要么全失败。REQUIRES_NEW用于“绝不能受外部事务影响”的内部操作典型就是操作日志——外部业务失败了日志不能跟着一起回滚否则排查问题连痕迹都没有。NESTED用于“局部回滚但不影响整体”的场景比如批量导入数据每一条记录是一个嵌套事务单条失败只回滚这条其余继续。我踩过的坑是把REQUIRES_NEW当成“解决内部异常不影响主流程”的万能药结果数据库连接被快速耗尽。因为REQUIRES_NEW会挂起当前事务并获取新的数据库连接如果内部方法被循环调用几千次近千个事务同时开启连接池扛不住。这时候应该考虑把日志收集改成异步批量写入而不是依赖传播行为强撑。3.Transactional的底层机制为什么它有时候“不听话”3.1 声明式事务背后的 AOP 代理很多人用Transactional多年但不知道它其实不是靠“拦截方法”实现的而是靠 Spring AOP 的代理机制。当一个 Bean 被 Spring 容器管理并且它的类或方法上标注了TransactionalSpring 会在启动阶段为这个 Bean 生成一个代理对象。外部调用这个 Bean 的方法时实际上调用的是代理对象的方法代理对象在拦截器中先开启事务再调用真实目标方法最后根据目标方法是否抛出异常来决定提交或回滚。这个拦截器层面的逻辑大致是// 伪代码理解思路即可 Object result null; TransactionStatus status platformTransactionManager.getTransaction(definition); try { result method.invoke(target, args); platformTransactionManager.commit(status); } catch (Exception e) { platformTransactionManager.rollback(status); throw e; }这里面有个关键点代理对象只在“外部调用”时生效。你在某个类的内部通过this.xxx()调用自己的另一个方法这个调用走的是真实对象直接跳过了代理Transactional自然失效。这是Transactional不生效的最常见原因后面单独展开。3.2 回滚规则的默认逻辑另一个高频误解是Transactional到底什么情况下回滚默认规则是只有抛出RuntimeException或Error时事务才会回滚抛出受检异常checked exception比如IOException、SQLException时事务不会回滚而是正常提交。这个设计让很多“业务异常用受检异常”的代码踩坑你在方法里throw new Exception(余额不足)结果事务提交了脏数据落库了。理解这个逻辑之后解决方案有两个。一是如果业务里用了自定义受检异常必须显式声明回滚条件Transactional(rollbackFor Exception.class) public void doSomething() throws Exception { // 业务逻辑 throw new BusinessException(余额不足); }二是在方法内部尽量抛非受检异常继承RuntimeException的子类比如自定义BusinessRuntimeException这样事务默认就会回滚。我个人的习惯是业务系统里统一使用RuntimeException子类作为业务异常既简化代码又避免这种误伤。3.3 事务管理器与连接同源Transactional之所以能生效底层靠的是PlatformTransactionManager。Spring 对不同持久层框架提供了不同实现DataSourceTransactionManager管理 JDBC/MyBatis 事务JpaTransactionManager管理 JPA/Hibernate 事务JtaTransactionManager管理分布式事务。其中最容易出问题的就是“连接同源”。DataSourceTransactionManager的事务实现本质是绑定在DataSource上的它从数据源里拿一个连接把autoCommit关闭然后把连接绑定到当前线程的 ThreadLocal 上。后续所有 DAO 操作只要用的是同一个DataSource就能拿到同一个连接从而共享同一个事务。如果你在事务方法里业务代码走的是数据源 A但某个内部组件悄悄切到了数据源 B这个操作就不在事务范围内提交回滚根本管不到它。多数据源环境下事务管理器的配置要格外注意这点。4. 事务失效的经典场景线上踩过的坑都在这里4.1 自调用导致的代理失效先看最经典的一个场景Service public class OrderService { public void createOrder() { // 业务逻辑 updateStock(); // this.updateStock() } Transactional public void updateStock() { // 扣库存逻辑 } }createOrder()内部调用this.updateStock()这个调用绕过了 Spring 生成的代理对象直接进入真实对象的方法所以updateStock()上的Transactional完全不会生效。事务失效异常发生时库存照样被扣。最让人头大的是这种问题在开发环境几乎测不出来——如果createOrder()内部没有异常数据看起来一切正常。解决方案有几个。最常见的是注入自己通过Autowired或Lazy注入自身的代理对象或者把事务方法拆分到另一个 Bean 里由外层 Bean 调用内层 Bean 的代理方法。我推荐的做法是拆类一个业务方法如果同时承担了“编排”和“事务”职责说明职责划分有问题拆出独立的 Service 类让事务边界清晰落在代理入口上。4.2 异常被吞掉或异常类型不是 RuntimeException事务回滚的前提是“异常能一路抛到事务拦截器”。如果业务代码自己把异常捕获并吞掉事务拦截器感知不到异常自然正常提交Transactional public void doBusiness() { try { // 业务逻辑 } catch (Exception e) { // log.error(...) 然后啥也不干异常被吞了 } }这种情况下数据库操作全部执行成功事务正常提交但业务其实已经“部分失败”了。类似的还有上面提到的受检异常问题catch (Exception e) { throw new Exception(...) }异常抛出了但抛的是受检异常默认不回滚。我的排查经验是遇到“加了事务但没回滚”的问题第一步看方法内是否有try-catch吞异常第二步看抛出的异常类型是不是RuntimeException子类第三步看rollbackFor有没有显式配置。这三个点覆盖了绝大部分“事务不回滚”案例。4.3 private、final、static 方法上的事务注解Transactional加在private方法上是无效的因为 Spring 的代理机制基于 CGLIB 或 JDK 动态代理private方法根本不会被代理拦截。final方法在 CGLIB 代理模式下也无法被重写同样不生效。static方法就更不用说了类级别的代理机制管不到静态方法。这个问题的深层原因是声明式事务依赖代理对方法的拦截所有“不能被代理重写”的方法都不适合作为事务入口。所以写代码时养成一个习惯Transactional只加在public、非final的实例方法上并且尽量加在 Service 层而不是 Controller 层。加在 Controller 上不是不能用而是事务边界会扩大到 HTTP 接口调用连接占用时间长不利于并发。4.4 多线程与事务传播在多线程场景下Spring 事务默认是不跨线程的。事务管理器通过 ThreadLocal 把连接绑定到当前线程你在主线程里开启事务新开的子线程里执行数据库操作用的是另一条线程拿不到主线程的 Connection事务自然不生效。子线程里的操作要么不在事务控制内要么是独立的新事务。处理跨线程事务的正确思路是主线程完成事务性操作后把结果提交再通过消息队列、异步任务等机制触发后续的独立操作。不要在异步线程里去操作主线程尚未提交的数据——读到的还是旧值。如果需要严格一致通常要引入分布式事务或消息可靠投递的机制。5. 编程式事务与嵌套控制什么时候放弃注解5.1 TransactionTemplate 解决精细控制Transactional虽然方便但事务边界在方法级不够灵活。比如一个方法里要循环处理多条数据每条数据独立开启一个事务单条失败不能影响其他条。如果硬要在循环体内写一个Transactional方法自调用问题又冒出来了拆类又显得代码结构臃肿。这时最干净的做法是使用TransactionTemplate。Service public class BatchImportService { Autowired private TransactionTemplate transactionTemplate; public void importBatch(ListItem items) { for (Item item : items) { try { transactionTemplate.execute(status - { // 每一条数据独立事务 saveItem(item); updateStock(item.getProductId()); return Boolean.TRUE; }); } catch (DataAccessException e) { // 记录失败项继续处理下一条 log.error(item {} import failed, item.getId(), e); } } } }TransactionTemplate的核心是execute方法它接收一个TransactionCallback内部会完成“开启事务、执行回调、正常提交、异常回滚”的完整流程。这样事务边界被精确圈定在回调代码块内循环体外不再受事务约束。5.2 编程式事务里的事务同步与回滚决策还有一类场景适合编程式事务同一个方法里一部分操作需要立即提交一部分需要延后到最终结果确定后再提交。用TransactionTemplate时你完全可以先在一个事务里执行第一条数据操作拿到结果后再决定要不要开启第二个事务去执行下一条。有个细节要注意TransactionTemplate在回调里如果返回null默认按提交处理如果抛出任何RuntimeException即使你在回调里 try-catch 了事务会回滚。实际使用中如果你想在回调里吞掉某个异常但让事务回滚需要显式调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。5.3 嵌套事务与保存点Savepoint前面讲的传播行为NESTED底层依赖数据库的 Savepoint 机制。如果一个事务内部调用了另一个嵌套事务嵌套事务会创建一个保存点嵌套事务失败时只回滚到保存点外层事务已经执行的其他操作依然保留。举个例子批量导入 100 条记录配置NESTED传播行为后第 50 条失败时前 49 条正常保留第 50 条所在的子事务回滚到保存点第 51 到 100 继续执行。这个语义和“每条记录独立事务”的区别在于它所有的子事务都在同一个物理事务里共享同一个数据库连接性能开销更小而独立事务需要反复获取/释放连接开销大得多。但NESTED有个前提底层数据库必须支持 Savepoint。MySQL 的 InnoDB 支持Oracle 支持但部分老数据库可能不支持。生产环境要用之前先确认数据库能力别想当然。6. 事务隔离级别脏读、不可重复读与幻读6.1 四个隔离级别的行为差异事务隔离级别解决的是并发事务之间的可见性问题。SQL 标准定义了四种级别分别对应不同的并发问题容忍度READ_UNCOMMITTED是最低的隔离级别允许一个事务读取到另一个事务尚未提交的数据。这个现象叫脏读极不推荐用于业务系统数据正确性完全没有保障。READ_COMMITTED是很多数据库的默认级别它解决了脏读但解决了不了一致性问题。事务 A 读取某条记录后事务 B 修改并提交了这条记录事务 A 再次读取会得到新的值这叫不可重复读。REPEATABLE_READ解决了不可重复读保证同一个事务内多次读取同一记录结果一致。但在这个级别下事务 A 查询一批数据后事务 B 插入了新的记录事务 A 再次查询会发现多了一行这叫幻读。MySQL 的 InnoDB 通过在REPEATABLE_READ级别引入间隙锁可以进一步阻止幻读所以实际上 MySQL 的默认级别可以看作是“可重复读 防幻读”。SERIALIZABLE是最高的隔离级别事务串行执行完全避免了并发冲突但性能损耗极大几乎不会用在常规业务中。6.2 隔离级别与锁的关系很多人在面试时背了四个隔离级别但到了实际选型发现 MySQL 默认REPEATABLE_READOracle/PostgreSQL 默认READ_COMMITTED系统的默认设置基本够用。真正需要调隔离级别的场景往往是某个特殊方法需要绝对一致性的快照读取才临时调高到SERIALIZABLE。有一点要特别提醒隔离级别本质上是通过锁和 MVCC 实现的级别越高锁竞争越激烈并发吞吐量越低。不要为了“更安全”而把所有事务都提升到SERIALIZABLE那等于把并发性能直接砍掉。实际工作中绝大多数业务系统用默认级别就够了真正出问题的大多是“没开事务”而不是“隔离级别不够”。6.3 只读事务与超时配置Transactional(readOnly true)是很多查询接口的标配它的意义在于向事务管理器声明“这个事务只需要查询”框架据此可以做优化。比如 Hibernate 会跳过脏数据检查底层 JDBC 也可能会有一些优化。但要注意readOnly true并不是数据库层面的强制只读。如果你在只读事务里执行了写操作不同数据库行为不一样有的会直接抛异常有的会静默允许。不要依赖这个属性做数据校验。另外事务只读标记只对“标记之后开启的事务”生效如果方法内自己又开了新事务新事务会覆盖只读设置。事务超时timeout属性指定的是事务允许执行的最大秒数超过时间会强制回滚并抛出异常。这个配置主要防止慢 SQL 长时间占着数据库连接拖垮连接池。实际项目中我习惯给核心写操作加上timeout 5类似的配置避免某个死锁或慢 SQL 把连接池吃光。7. 多数据源与分布式场景事务的边界在哪里7.1 多数据源下的事务管理器配置一旦系统引入多个数据源事务就面临“哪个数据源管哪个事务”的问题。Spring 里配置多个DataSourceTransactionManager时Transactional默认使用PlatformTransactionManager按类型匹配。如果容器里只有一个事务管理器那没啥问题如果有两个就必须显式指定Transactional(transactionManager orderTransactionManager) public void createOrder() { // 操作 order 库 // 操作 user 库不在当前事务内 }这里要理解一个残酷的现实Spring 的单数据源事务管理不了跨多个数据源的数据一致性。Transactional只能保证它绑定到的那个DataSource连接上的操作具备原子性。操作另一个数据源时事务管理器管不到数据写一半照样会留下半截。所以多数据源业务里要么严格划分事务边界确保一个事务只操作一个数据源要么接受“最终一致性”方案引入分布式事务中间件。7.2 本地消息表与最终一致性分布式事务的经典方案绕不开本地消息表。这个方案的核心思想是在本地事务里同时写入业务数据和一条“待发送消息”然后通过后台任务异步把消息投递到其他系统。比如扣款成功后本地事务里插入一条“通知订单系统扣款成功”的消息记录消息投递成功后再把消息状态改为已发送。这个方案实现简单、理解容易不需要引入额外中间件真正做到了业务数据与消息数据的强一致——因为它们在同一个本地事务里。缺点是消息表可能有积压且需要后台任务持续捞取存在消息丢失的窗口期好在可以通过消息表状态和重复消费幂等性来兜底。7.3 事务消息与补偿机制的取舍如果项目已经引入了消息队列中间件可以使用事务消息方案先发送半消息执行本地事务然后确认消息可投递。消息队列保证半消息不被消费者看到直到本地事务完成后才真正投递。这个方案比本地消息表更优雅但依赖中间件能力且需要做好消费者的幂等处理。无论用哪种方案都要想清楚一个核心问题分布式系统里没有绝对的强一致只有最终一致。业务设计上能不能接受“短暂不一致但最终对账一致”能接受就可以大胆采用消息方案不能接受就要考虑全局事务中间件但那也意味着性能牺牲不是免费的午餐。我个人的工程经验是优先从业务设计上避免跨库强一致。如果能把需要跨库的数据操作拆成“单库事务 消息通知”的模式系统会简单可靠得多。分布式事务中间件是最后的武器不是第一选择。8. 事务排查与调优实录一份实战问题速查表8.1 排查问题时的标准动作遇到线上“事务没生效”的反馈我建议按固定顺序排查别一上来就怀疑框架。第一步确认方法是不是public且没被final修饰第二步看方法是不是被同类内部this调用第三步检查异常有没有被捕获或抛成了受检异常第四步确认rollbackFor是否配置齐全第五步查看事务管理器能否正确匹配到目标数据源。走完这五步90% 的事务问题都能定位。剩下的 10% 基本是配置问题比如EnableTransactionManagement没开启、多个事务管理器没指定transactionManager、Spring 容器里代理没生成成功等。8.2 日志里怎么验证事务开启排查事务问题日志是最有力的证据。Spring 事务管理器默认会输出 DEBUG 级别日志日志关键字包括 “Getting transaction for”、“Participating in existing transaction”、“Applying rollback”。在配置文件里把org.springframework.transaction的日志级别调到 DEBUG运行一段业务代码就能看到事务边界在哪、是否加入已有事务、回滚是否触发。实际生产环境里不可能长期开着 DEBUG但在测试环境或排查窗口期临时开启效果立竿见影。这是我的首选排查方式。8.3 性能与隐患调优建议事务不是越宽越好。事务范围过大意味着多个 SQL 长时间持有数据库锁并发冲突概率增大连接池被占用的时间变长系统吞吐量直接下降。我见过一个事故一个报表接口聚合了 20 条 SQL每条处理时间 100ms结果这个接口在高峰期把连接池拖垮整个应用不可用。问题根源就是事务粒度太大把大量无关查询也圈进了事务。调整思路是事务内只放必要的写操作查询操作放到事务开启前或提交后。读多写少的接口可以走只读事务甚至无事务多表写操作尽量保持事务内 SQL 数量少而精。用一句话总结事务里只做“必须一起成功或一起失败的写操作”其他操作一律别蹭事务。8.4 常见问题速查表现象可能原因解决方案方法抛异常但数据没回滚异常被 try-catch 吞掉去掉吞异常代码或显式throw new RuntimeException抛了业务异常但事务提交了异常是受检异常Transactional(rollbackFor Exception.class)或用RuntimeException子类Transactional完全不生效方法为 private/final 或自调用改为 public 方法拆类调用代理对象子线程里操作数据没回滚事务跨线程失效别在子线程操作未提交数据用独立消息机制同事务内调REQUIRES_NEW连接耗尽REQUIRES_NEW每次获取新连接避免循环内大量REQUIRES_NEW改用NESTED或批量异步处理多数据源下事务失效未指定事务管理器Transactional(transactionManager ...)显式指定只读事务里能写入数据库readOnly非强制约束靠代码规范保证不依赖该属性防写这张表基本覆盖了我这几年来在生产环境和面试中被问过的高频问题。你可以把它截图收藏或者打印出来贴在工位上下次踩坑先照表自查一遍。9. 最后分享一点个人体会从我这几年的经验来看事务这个知识点的难点从来不在概念理解上而在于“你以为你理解了但生产环境不给你解释的机会”。Spring 事务的坑大多是隐性的静默失效比直接抛异常更危险。所以我对团队的要求是写事务代码的人必须能回答三个问题——这个事务的边界在哪异常路径上有没有吞异常这个方法是否会被内部自调用能把这三点想清楚Spring 事务的大部分坑基本就绕过去了。后面如果你们接入了微服务或者消息中间件事务的问题会从“单库事务”升级成“分布式数据一致性”那又是一个全新的战场。建议先把单库事务吃透再逐步接触分布式事务方案循序渐进比一步到位靠谱得多。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号