电脑港
白蓝主题五 · 清爽阅读
首页  > 软件应用

事务管理怎么做稳妥?几个实战中踩过坑才摸出来的招儿

小王上周上线了个订单退款功能,结果用户一并发三笔退款,数据库里库存多扣了两次——不是代码没写事务,是事务用得不稳当。事务管理真不是加个@Transactional就万事大吉的事。

别把事务当开关,它是个状态机

很多人以为「开了事务」= 「自动兜底」,其实事务本身有生命周期:开启、执行、提交或回滚。一旦中间某个环节卡住(比如远程调用超时、手动抛了非运行时异常、甚至忘了写return导致后续逻辑意外执行),事务可能早早就提交了,或者压根没生效。

Java Spring 默认只对 RuntimeExceptionError 回滚,你写个 new Exception("查无此单") 抛出去?事务纹丝不动。得显式写:

@Transactional(rollbackFor = Exception.class)

嵌套调用?别信「默认传播」

ServiceA.methodA() 调 ServiceB.methodB(),两个方法都标了 @Transactional,你以为 B 出错 A 就跟着回滚?不一定。Spring 默认传播行为是 REQUIRED,意思是「有就用,没有就建」。如果 A 已经启了事务,B 就直接复用——看着像一层,实际还是同一个事务上下文,B 挂了 A 确实会滚。但要是 B 改成 REQUIRES_NEW,那就另起炉灶,A 的事务和 B 完全隔离,B 滚了 A 不受影响,B 成功了 A 还可能因为别的原因滚掉——这时候数据一致性反而难保。

真实场景:支付回调里要更新订单+发消息,发消息失败不能拖垮订单状态更新。这时才适合给发消息方法加 REQUIRES_NEW,再配个本地消息表或死信队列兜底。

跨库、跨服务?别硬扛,分阶段来

订单库和库存库不在一块儿,一个事务管不了。硬上分布式事务框架(比如 Seata)?中小项目容易被复杂度反杀。更稳妥的做法是「本地事务 + 补偿」:先在订单库落单(事务保证),再发 MQ 异步扣库存;库存服务收到后自己用本地事务执行,成功回执,失败则由定时任务扫描未终态订单,主动调接口重试或告警人工介入。

关键点就一句:**让每一步都有明确的终态标识,且能被反复查询和驱动**。比如订单表加个 stock_status 字段,值为「待扣减」「已扣减」「扣减失败」,比靠日志猜强一百倍。

别忽略连接和超时

数据库连接池最大连接数设 20,事务里调了个慢接口耗时 3 秒,10 个并发进来,后面请求全卡在获取连接上,等不及就超时失败。这不是事务逻辑问题,是资源瓶颈暴露了设计盲区。

建议:事务内只做 DB 操作和轻量校验;HTTP 调用、文件读写、复杂计算这些,统统挪到事务外,用「先预留、再确认」的方式衔接。比如创建订单时生成预支付号(事务内),支付成功后再异步更新订单支付状态(事务内)。