You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将转账业务多步数据库操作合并为单个事务保证数据一致性

改造方案说明

首先明确核心前提:第三方转账接口属于外部IO操作,耗时不可控,绝对不能包裹在数据库事务内部,否则会导致事务长期持有行锁,引发大量请求阻塞甚至数据库雪崩。我们通过拆分两个独立的原子本地事务+异常兜底的方式,即可保证整个流程的数据一致性。


具体执行流程(含事务边界定义)

  1. 第一个本地事务:转账预处理

    • 流程开始后先执行BEGIN开启事务
    • 执行更新冻结余额的SQL:
      UPDATE User SET hold_balance = hold_balance + 100 WHERE name = 'andy' AND balance >= 100;
      

      这里额外加balance >=100的判断,是为了从数据库层面拦截余额不足的非法转账请求,避免超扣

    • 执行插入交易记录的SQL:
      INSERT INTO Tx (name, amount, status) VALUES ('andy', 100, 'IN_PROGRESS');
      
    • 以上两个操作都执行成功就执行COMMIT提交事务,任意一步报错都执行ROLLBACK回滚,整个预处理流程直接终止,不会进入后续步骤
    • 事务提交后,再调用第三方转账接口
  2. 第二个本地事务:结果确认&数据更新
    不管第三方接口调用成功、失败、超时还是抛出异常,甚至服务中途中断重启,都要执行这个原子事务:

    • 先执行BEGIN开启事务
    • 先对当前操作的用户和交易记录加行锁,避免并发修改:
      SELECT hold_balance, balance FROM User WHERE name = 'andy' FOR UPDATE;
      SELECT status FROM Tx WHERE id = [刚才插入的交易ID] FOR UPDATE;
      
    • 执行释放冻结余额的SQL:
      UPDATE User SET hold_balance = hold_balance - 100 WHERE name = 'andy';
      
    • 再根据第三方接口的返回结果分支处理:
      • 接口调用成功:
        • 执行扣减余额的SQL:
          UPDATE User SET balance = balance - 100 WHERE name = 'andy';
          
        • 执行更新交易状态的SQL:
          UPDATE Tx SET status = 'OK' WHERE id = [交易ID];
          
      • 接口调用失败/超时/抛出异常:
        • 仅执行更新交易状态的SQL:
          UPDATE Tx SET status = 'FAILED' WHERE id = [交易ID];
          
    • 以上所有操作执行成功就执行COMMIT提交事务,任意一步报错都执行ROLLBACK回滚即可。

极端异常兜底机制

如果服务在调用第三方接口之后、第二个事务提交之前永久中断,导致交易状态一直停留在IN_PROGRESS,只需要加一个简单的定时调度任务即可兜底:

  • 定期扫描创建时间超过阈值(比如5分钟)且状态为IN_PROGRESS的交易记录
  • 调用第三方接口查询该笔交易的实际执行结果,再按照上述第二个事务的逻辑补全数据变更,就能保证最终一致性。

内容的提问来源于stack exchange,提问作者nametal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 02:06:00