为什么远程调用别包进 Spring 事务里

为什么远程调用别包进 Spring 事务里

这两天看 interview-guide 里的 Agent 执行链路,我又把一个老问题重新确认了一遍:@Transactional 不是"整段流程保护罩",它更适合包住短而确定的本地状态变更,不适合顺手把远程模型调用、工具调用这类慢操作一起包进去。

很多人第一次写这类流程时,很容易觉得"这一整轮要么全成功,要么全失败",于是想把 chat() 整体放进一个事务里。直觉上很完整,实际上风险很高。因为数据库事务一旦拉长,连接、锁、行版本和回滚成本都会一起拉长;而远程调用偏偏最不稳定,慢的时候慢,失败的时候还不一定能按你的预期回滚。

interview-guide 这里的处理我觉得很对路。AgentOrchestrator.chat() 在注释里写得很明确:远程模型调用和工具调用都放在事务外,只在 turn 的开始和结束阶段做持久化,见 interview-guide/app/src/main/java/interview/guide/modules/agent/service/AgentOrchestrator.java:93。对应的本地持久化被拆到了 AgentSessionService.startTurn()waitForApproval()completeTurn()failTurn() 这些短事务里,见 interview-guide/app/src/main/java/interview/guide/modules/agent/service/AgentSessionService.java:91:117:179:213

事务里最怕的不是报错,是"慢且不确定"

如果你把一次远程调用包进事务,最直接的问题不是"失败会不会回滚",而是这段时间数据库资源一直被你占着。连接不会因为你在等 HTTP 响应就自动释放,锁也不会因为你在等模型吐字就先让别人用。并发一上来,吞吐就会掉得很明显。

这还只是性能问题。更麻烦的是语义问题。数据库事务只能回滚数据库里的状态,回滚不了已经发出去的外部副作用。比如消息已经投出、第三方接口已经收到了请求、远程工具已经执行了一半,这时候你本地事务回滚了,也只是把"我这里的记录"抹掉,不代表外部世界真的回到原点。

所以面试里如果有人问"为什么不要把 RPC、HTTP、消息发送包进事务",核心答案其实就两句:

  • 事务持有时间会被外部依赖放大,带来锁等待、连接占用和超时风险。
  • 数据库事务不能回滚外部副作用,强行包进去只会制造"本地回滚了,但外部已经动了"的假一致性。

更稳的做法,是把事务收缩到状态切换点

这个项目里,startTurn() 只做几件本地且必要的事:回收过期 turn、拒绝并发冲突、创建新 turn、先落用户消息,再刷新 session 更新时间,见 interview-guide/app/src/main/java/interview/guide/modules/agent/service/AgentSessionService.java:92-112。这就是一个很典型的"短事务开场"。

后面的模型决策、工具执行、审批恢复都在事务外跑。等结果拿到了,再用另一个短事务做收口。比如 completeTurn() 的顺序是先写 memory,再追加 assistant 消息,最后推进 turn 终态,见 interview-guide/app/src/main/java/interview/guide/modules/agent/service/AgentSessionService.java:180-208。这个顺序很有工程味,因为它不是追求"一个大事务包住一切",而是追求"每次落库时都尽量保证局部状态自洽"。

审批挂起那段也一样。AgentApprovalRuntimeService.parkTurnForApproval() 用一个本地事务把 trace、approval、turn 一次性推进到 WAITING_APPROVAL,但它明确不把远程模型调用或工具执行包进数据库事务,见 interview-guide/app/src/main/java/interview/guide/modules/agent/service/AgentApprovalRuntimeService.java:19-21:32-74。这其实就是把"状态切换原子化"和"外部执行解耦"分开做。

真正该追求的不是"大一统回滚",而是可恢复

很多系统走到后面,都会从"幻想一次事务包完全部流程"转向"承认流程会中断,所以把恢复路径设计好"。这个仓库里的 turn、approval、trace 其实就是这个思路。

远程调用放事务外,并不等于放弃一致性,而是把一致性的重点从"单事务全包"转成"关键状态有明确边界,失败后能恢复、能补偿、能重试"。比如 turn 有 RUNNINGWAITING_APPROVALCOMPLETEDFAILED,审批恢复时还会先 claim 执行权,避免重复推进同一个 turn,见 interview-guide/app/src/main/java/interview/guide/modules/agent/service/AgentSessionService.java:162-175。这比一个长事务更接近真实线上系统。

所以如果把这件事压缩成一句面试回答,我会这么说:

"@Transactional 适合保护短事务里的本地状态一致性,不适合包远程调用。远程调用会拉长锁持有时间,而且数据库事务回滚不了外部副作用。更稳的做法是把事务收缩到状态变更点,用显式状态机、补偿和恢复机制处理长链路。"

这类题表面上是事务题,本质上其实是你有没有真的做过线上链路题。🙂

相关推荐
快乐非自愿1 小时前
SpringAI入门指南
大数据·人工智能·spring
人道领域1 小时前
【LeetCode刷题日记】225.用队列实现栈--三招实现栈操作(多种思维)
java·开发语言·算法·leetcode·面试
Mr_pyx1 小时前
【告别for循环】Java Stream 流式编程精通:从入门到源码级的性能优化
java·开发语言·性能优化
:1211 小时前
java基础--数组
java·开发语言
Agent产品评测局2 小时前
智能体在药物发现阶段如何辅助完成靶点专利覆盖的自动识别?2026药研AI Agent全景盘点与自动化选型指南
java·人工智能·ai·chatgpt·自动化
武子康2 小时前
大数据-277 Spark MLib-梯度提升树(GBDT)算法原理与工程实现指南
大数据·后端·spark
Agent手记2 小时前
终端消费数据自动采集与分析智能体的搭建思路:2026全链路技术架构与实战解析
java·开发语言·人工智能·ai·架构
这是程序猿2 小时前
mysql的安装教程
java·人工智能·windows·mysql
小Y._2 小时前
Spring Boot 4.0 发布:Jackson 3 强制迁移、虚拟线程原生支持、弹性能力一文搞定
java