Java 后端事务:从 Spring 代理到幂等、状态机和补偿
Java 后端事务从 Spring 代理到幂等、状态机和补偿开头很多人复习事务时会先背几个词ACID 传播级别 隔离级别 脏读 幻读 分布式事务这些当然要会但如果只停在概念面试里很容易被追问打散。更接近真实业务的理解应该是一次业务操作 ├── 中间失败怎么办 - 事务 ├── 同时有多个请求怎么办 - 锁 / 条件更新 ├── 重复请求怎么办 - 幂等 ├── 状态能不能这样变 - 状态机 ├── 跨服务怎么办 - 分布式事务 / MQ / Outbox └── 半成功后怎么办 - 补偿也就是说事务不是孤立知识点。它经常和锁、幂等、状态机、消息和补偿一起出现。一、事务到底解决什么问题事务最核心的作用是保证一组数据库操作要么全部成功要么全部失败。比如一个下单动作创建订单 扣减库存 生成支付记录如果订单创建成功库存扣减失败系统状态就不一致了。所以这些数据库操作通常要放在一个本地事务里。数据库本身提供事务能力begin;insertintoorders(...);updatestocksetquantityquantity-1wheresku_id?;insertintopayment_record(...);commit;Spring 的Transactional不是替代数据库事务而是帮我们管理事务边界避免每个方法都手写begin、commit和rollback。二、ACID 不要只背定义事务有四个经典特性ACID。特性含义例子Atomicity 原子性一个事务里的操作不可分割转账时 A 扣钱和 B 加钱必须一起成功Consistency 一致性事务前后满足业务规则转账前后总金额不变Isolation 隔离性并发事务之间不能产生不可接受的互相干扰两人同时买最后一件商品不能都扣成功Durability 持久性提交后数据持久保存支付提交后服务重启订单仍是已支付面试里比较好的回答方式是事务是一组数据库操作的执行单元保证这些操作满足 ACID。比如下单时订单、库存、支付记录必须保持一致如果中间失败需要整体回滚。三、Spring 事务是怎么实现的Spring 声明式事务的关键是AOP 代理 事务拦截器 数据库事务管理器一次正常调用大概是Controller - Spring 代理对象 - 开启或加入事务 - 调用真实 Service 方法 - 正常返回提交 - 抛出异常按规则回滚可以理解成下面这个伪代码publicObjectinvoke(){beginOrJoinTransaction();try{Objectresulttarget.method();commit();returnresult;}catch(Throwableex){if(shouldRollback(ex)){rollback();}else{commit();}throwex;}}所以Transactional的关键不是“标了就一定生效”而是调用必须经过 Spring 代理。四、Transactional常见失效场景1. 同类内部调用ServicepublicclassOrderService{publicvoidcreateOrder(){saveOrder();}TransactionalpublicvoidsaveOrder(){// insert order}}createOrder()里调用saveOrder()本质是this.saveOrder()没有经过 Spring 代理所以事务逻辑可能不会生效。常见解决方式是拆成两个 Service让调用经过 Spring Bean。2. 异常被吃掉TransactionalpublicvoidcreateOrder(){saveOrder();try{deductStock();}catch(Exceptione){log.error(deduct stock failed,e);}}异常被捕获后方法正常返回Spring 会认为可以提交事务。关键失败不能只打印日志要继续抛出异常或者显式标记回滚。3. 异常类型不匹配Spring 默认对RuntimeException和Error回滚。对于 checked exception如果业务上也要求回滚需要显式配置Transactional(rollbackForException.class)4. 新线程和远程调用Spring 事务上下文通常绑定在线程上。新开的线程不会自动继承外层事务。远程接口、MQ、WebSocket、第三方支付也不属于本地数据库事务。它们需要幂等、消息、重试和补偿设计。五、事务传播级别传播级别解决的问题是一个事务方法调用另一个事务方法时内层方法应该加入外层事务、新开事务、非事务执行还是要求必须有/没有事务Spring 一共有 7 种传播级别传播级别有外部事务时无外部事务时常见用途REQUIRED加入当前事务新建事务默认最常用REQUIRES_NEW挂起外部事务新建事务新建事务独立日志、失败记录NESTED在外部事务内建保存点类似REQUIRED局部回滚SUPPORTS加入当前事务非事务执行可复用查询NOT_SUPPORTED挂起外部事务非事务执行非事务执行非事务长操作MANDATORY加入当前事务抛异常强制由上层提供事务NEVER抛异常非事务执行明确禁止事务1.REQUIRED默认级别有事务就加入没有事务就创建。适合订单创建这类必须一起成功失败的业务。2.REQUIRES_NEW不管外部有没有事务都新建一个独立事务如果外部有事务先挂起外部事务。典型场景订单事务 T1 - 保存订单 - 挂起 T1 - 日志事务 T2 提交 - 恢复 T1 - T1 后续失败回滚这样即使订单失败日志也可能保留下来。但不要滥用。比如订单和库存应该一起成功失败如果把库存扣减写成独立REQUIRES_NEW可能出现库存已扣 订单回滚3.NESTEDNESTED是保存点不是完全独立事务。外层事务 T1 - 保存订单 - 建立 savepoint - 发优惠券失败 - 回滚到 savepoint - 订单继续如果外层事务最终回滚内层保存点里的内容也会跟着回滚。六、事务隔离级别隔离级别解决并发事务之间的可见性问题。三个常见并发现象问题含义脏读读到别人未提交的数据不可重复读同一事务两次读同一行结果不同幻读同一事务两次范围查询记录集合不同四种隔离级别隔离级别说明READ UNCOMMITTED读未提交可能脏读READ COMMITTED读已提交避免脏读REPEATABLE READ可重复读MySQL InnoDB 默认SERIALIZABLE串行化隔离最强并发最低MySQL InnoDB 里要特别注意普通 SELECT - 快照读依赖 MVCC UPDATE / DELETE / SELECT ... FOR UPDATE - 当前读读取最新数据并加锁所以不要简单说“可重复读解决一切幻读”。更严谨的说法是InnoDB 在REPEATABLE READ下通过 MVCC 提供一致性快照对锁定读和更新操作会结合行锁、间隙锁和 next-key lock 控制并发。七、幂等重复执行也不能重复产生副作用幂等的核心是同一个业务请求执行一次和执行多次最终对系统的业务影响一致。重复请求不只来自用户重复点击还可能来自网络超时重试。RPC 重试。支付平台重复回调。MQ 至少一次投递。定时任务重跑。补偿任务重复执行。1. HTTP 方法的幂等性按常见 REST 语义方法常见含义是否天然幂等GET查询是PUT整体替换或确定性更新通常是DELETE删除资源从最终状态看通常是POST创建或提交动作通常不是PATCH部分更新不天然是取决于设计注意这只是语义约定。真正是否幂等还要看服务端怎么实现。2. 常见幂等方案唯一业务号 唯一索引比如支付流水号createuniqueindexuk_payment_noonpayment_record(payment_no);第一次插入成功重复插入违反唯一约束。重要业务要尽量把幂等兜底放在数据库层。状态机 条件更新支付回调不要无条件更新updateorderssetstatusPAIDwhereid?andstatusWAIT_PAY;第一次回调影响 1 行重复回调影响 0 行。幂等表MQ 消费常用message_consume_record ├── message_id ├── business_id ├── consume_status └── consume_time先记录消息是否处理过再执行业务。业务执行和消费记录最好放在一个本地事务里。Token 令牌适合短周期防重复提交获取 token - 提交时携带 token - 服务端校验并删除 token乐观锁updateapprovalsetstatusPASSED,versionversion1whereid?andversion?andstatusWAIT_AUDIT;更新失败说明数据已经被别人改过。分布式锁分布式锁解决的是“同一时间只允许一个请求进入”。它不是完整幂等。锁过期后重复请求仍然可能再次进入。所以重要业务还要有唯一键、状态条件更新或幂等表兜底。八、状态机控制业务能不能这样变化很多业务系统本质上都是状态流转。订单WAIT_PAY - PAID - COMPLETED审批WAIT_AUDIT - PASSED / REJECTED任务WAITING - RUNNING - SUCCESS / FAILED状态机解决的是当前状态是否允许迁移到目标状态。它和幂等经常结合在一起updateorderssetstatusPAIDwhereid?andstatusWAIT_PAY;这条 SQL 同时表达只有 WAIT_PAY 才能变成 PAID 重复 PAID 不再执行 并发更新只能有一个成功九、事务、锁、幂等的关系这三个东西经常被混在一起。1. 事务解决一次请求内部多张表操作是否一起提交或回滚。2. 锁解决同一时间多个请求是否可以同时进入同一资源的关键区。3. 幂等解决同一个业务请求重复执行是否会重复产生业务副作用。支付例子事务订单和支付记录一起更新 锁避免同一订单同一时间多次处理 幂等支付平台重复回调也不会重复发权益 状态机订单只能从待支付变已支付 补偿已支付但订单未归并时后续任务修复十、分布式事务跨服务后本地事务不够了单库里订单表 库存表 支付表可以用一个本地事务。拆成服务后订单服务 - order_db 库存服务 - stock_db 支付服务 - pay_db一个普通Transactional管不了三个数据库也管不了远程服务。这时有几类方案。1. XA / 2PC两阶段提交Prepare所有参与者准备 Commit/Rollback统一提交或回滚优点是一致性强缺点是性能和可用性压力大可能阻塞资源。2. TCCTry预留资源 Confirm确认 Cancel取消余额扣减例子Try冻结 100 Confirm扣除冻结金额 Cancel释放冻结金额优点是业务可控缺点是每个业务都要实现三套动作还要处理幂等、空回滚和悬挂。3. SagaSaga 把一个长事务拆成多个本地事务。每一步成功后进入下一步失败时执行补偿动作。适合允许最终一致的长流程。4. MQ 最终一致性订单和库存例子订单服务本地事务 - 创建订单 - 写出待发送事件 消息投递 - 库存服务消费 - 幂等扣减库存 - 失败重试或补偿这里真正要保证的是消息不能丢。消费端必须幂等。失败要重试。长期失败要告警或人工处理。5. Outbox 本地消息表Outbox 的思路是同一个本地事务里 1. 修改业务表 2. 写入 outbox 消息表 事务提交后 3. 后台任务投递消息 4. 投递成功后标记消息状态这样避免“数据库提交了但消息没发出去”。但投递任务可能重复发送所以消费者仍然要幂等。6. SeataSeata 是常见分布式事务框架提供 AT、TCC、Saga、XA 等模式。面试时可以知道它解决哪类问题但如果项目里没有真实落地不要把它说成生产经验。十一、几个面试综合题1. 下单时如何保证订单和库存一致可以分层回答如果订单和库存在同一个数据库里可以用本地事务包住订单创建和库存扣减。库存扣减不能只先查再改应该使用带库存条件的原子更新例如quantity 1根据影响行数判断是否扣减成功。重复提交要有订单号或请求号幂等。如果订单和库存拆成不同服务普通本地事务就不够了可以使用可靠消息或 Outbox 实现最终一致库存消费端要幂等失败后重试或补偿。2. 支付回调重复通知怎么办推荐回答支付回调要先校验外部流水号、金额和订单状态。状态推进使用条件更新只允许待支付变成已支付。重复回调时影响 0 行不再重复执行业务动作。后续如果还有发权益、订单归并等动作要么在本地事务内完成要么通过消息、Outbox 或定时补偿保证最终一致。3. MQ 消费两次怎么办推荐回答MQ 通常是至少一次投递消费者必须幂等。可以用消息 ID 或业务 ID 建消费记录表先插入消费记录成功才执行业务如果重复消息再次到达发现已处理就直接返回。业务处理和消费状态更新要注意放在同一个本地事务里。4. 为什么用了事务还要锁推荐回答事务解决一次请求内部的提交和回滚锁解决多个请求是否能同时进入同一资源的关键区。比如库存只剩 1 件两个请求同时读到可用事务本身不一定阻止它们都进入扣减逻辑。可以用锁降低并发进入但最终仍要用数据库条件更新和唯一约束兜底。十二、不要这样回答这些说法很容易被面试官追问击穿“加了Transactional就能保证所有一致性。”“Redis 锁就是幂等。”“先查一下有没有有就不插入这样就防重复了。”“REQUIRES_NEW更安全所以所有内部方法都用它。”“MySQL 可重复读就是完全没有幻读。”“用了 MQ 就自然最终一致。”“分布式事务就是上 Seata所有场景都适合。”更好的习惯是先判断问题类型再选择对应方案。十三、结尾事务这块真正要建立的是一张图本地失败 - 事务 并发进入 - 锁 / 原子条件更新 重复执行 - 幂等 状态变化 - 状态机 跨服务 - 分布式事务 / MQ / Outbox 半成功 - 补偿能把这张图讲清楚再结合订单、支付、库存、任务同步这些通用场景事务面试就不再是零散背题而是一个完整的后端一致性模型。

相关新闻

基于Veyforge物理AI合成的Sim2Real高校实训方案-TsingtaoAI

基于Veyforge物理AI合成的Sim2Real高校实训方案-TsingtaoAI

物理 AI 合成驱动,让机器人在虚拟世界中成长,在现实世界中落地。面向高校的物理AI合成数据、高保真仿真与真机迁移一体化实训——TsingtaoAI VeyForge 项目团队 一、行业背景 Embodied AI正在推动人工智能从“能思考”走向“能动手”。机器人、无人车、…

2026/10/20 8:35:13 阅读更多 →
Spring Boot 3.x参数解析问题解决方案

Spring Boot 3.x参数解析问题解决方案

1. 问题现象与背景分析最近在升级到Spring Boot 3.x版本后,不少开发者遇到了控制器方法无法正确接收请求参数的问题。具体表现为:当使用RequestParam或直接声明方法参数时,前端传递的参数值在后端接收时变成了null。这个问题在Spring Boot 2.…

2026/10/19 19:41:15 阅读更多 →
2026年内蒙古智慧燃气安全监测管理系统的建设与服务商观察

2026年内蒙古智慧燃气安全监测管理系统的建设与服务商观察

燃气安全监测放在内蒙古版图上思考,秤砣就得往"广"和"冷"两个方向多压几分。内蒙古东西跨度超过两千四百公里,从城市门站到旗县终端,任何监测盲区都可能因信息传不回来变成管理黑洞。每年十月到次年四月漫长采暖季&#…

2026/10/16 19:55:23 阅读更多 →

最新新闻

模型量化如何影响推理性能?实战解析原理、选型与问题排查

模型量化如何影响推理性能?实战解析原理、选型与问题排查

在AI推理优化这条路上,量化是我见过投入产出比最高的手段。很多人以为量化就是把模型从FP32变成INT8,体积缩小、推理变快,但实操后才发现,AI模型量化对推理性能的影响并不只有正向:有时速度反而下降,精度却…

2026/10/20 20:20:51 阅读更多 →
基于Simulink的23自由度分布式电驱动整车动力学模型搭建与仿真

基于Simulink的23自由度分布式电驱动整车动力学模型搭建与仿真

做分布式电驱动车辆的控制,最头疼的问题就是你手上的整车模型到底该建多细。我这次在Matlab Simulink里完整搭了一套23自由度整车动力学模型,把车身六个方向的运动、四个车轮的垂向跳动、车轮旋转、转向系统、以及分布式驱动链条的扭转自由度全部拉进来了…

2026/10/20 20:20:51 阅读更多 →
用JSON和Python命令行构建个人技能管理工具:从数据建模到实践

用JSON和Python命令行构建个人技能管理工具:从数据建模到实践

1. 为什么需要技能管理工具:需求拆解与设计取舍上周末想更新简历,结果发现我根本说不清“现在到底会什么”。十几项技能分散在旧简历、脑图、收藏夹和一摞笔记本里,有的早已过期,有的只停留在“看过教程”的阶段,还有的…

2026/10/20 20:20:51 阅读更多 →
企业微信回调验证与AES加解密:.NET开发者接入指南

企业微信回调验证与AES加解密:.NET开发者接入指南

接手过几个企业微信自建应用的项目,每次新人对接时几乎都会卡在同一个地方:管理后台保存“接收消息服务器”配置时提示URL验证失败,或者明明验证过了,消息却一直推送不进来。这个环节看着只是填几个参数,背后实际上是一…

2026/10/20 20:20:51 阅读更多 →
vLLM Prefix Caching 原理:如何用 Paged KV 消除重复计算

vLLM Prefix Caching 原理:如何用 Paged KV 消除重复计算

先说一个我遇到过的情况。某个线上推理服务,所有请求都带着同一段两千多字的 system prompt,并发一上来,GPU 利用率看着很高,但真正对用户有用的计算没多少——大部分算力在一遍遍重复计算同一段内容的 K 和 V。很长一段时间里&am…

2026/10/20 20:20:51 阅读更多 →
用Design.md构建符合产品风格的原型设计:设计令牌实战指南

用Design.md构建符合产品风格的原型设计:设计令牌实战指南

上周跟某团队聊原型评审,对方把三个页面的设计稿拍在我面前,主色蓝就出现了五种,没有两个颜色值相同。评审会开了一个小时,快一半时间在争论“这个蓝到底是不是我们品牌那个蓝”。原型阶段最让人心累的从来不是画得快不快&#xf…

2026/10/20 20:19:47 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →