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

如何使Supabase代码符合ACID原子性:chargeUser失败时回滚插入

优化方案:实现符合ACID原子性的数据库事务

原代码的核心问题在于先执行了purchases表的插入操作,再调用chargeUser,一旦chargeUser执行失败,已经插入到数据库的记录无法回滚,而且AbortController在这里完全无效——因为插入请求已经完成,再调用abort()根本无法撤销已提交的数据库操作。

要实现原子性(要么两个操作都成功,要么都失败),我们需要利用Supabase基于PostgreSQL的事务能力,将数据库操作与关键业务逻辑(chargeUser)纳入统一的事务控制中。以下是具体优化步骤:

优化后的代码

export const addTransaction = async (newPurchase: PurchaseType) => {
  // 开启数据库事务
  const { data: transaction, error: transactionError } = await supabase.rpc('begin');
  if (transactionError) throw new Error('开启事务失败:' + transactionError.message);

  try {
    // 在事务内插入采购记录
    const { error: insertError } = await supabase
      .from("purchases")
      .insert(newPurchase);

    if (insertError) throw new Error(insertError.message);

    console.log('采购记录已在事务中暂存');

    // 执行用户扣费操作
    await chargeUser(newPurchase.amount, newPurchase.id!);

    // 所有操作成功,提交事务
    const { error: commitError } = await supabase.rpc('commit');
    if (commitError) throw new Error('提交事务失败:' + commitError.message);

    console.log('事务提交成功,采购记录已永久保存');
  } catch (error: any) {
    console.error('操作失败,回滚事务:', error.message);
    // 回滚事务,撤销所有未提交的数据库操作
    await supabase.rpc('rollback');
    throw new Error("ล้มเหลวระหว่างการบันทึกการสั่งซื้อสินค้า");
  }
};

关键说明

  • 事务的原子性保障:通过begin开启事务后,所有数据库操作(如insert)都会被暂存,只有调用commit后才会永久生效;如果任何步骤失败,调用rollback会撤销所有已执行的数据库操作,确保数据一致性。
  • 原代码的AbortController误区:AbortController只能中止未完成的HTTP请求,但原代码中插入操作已经执行完成后才调用abort(),完全无法回滚已提交的数据库变更,因此优化后移除了无用的AbortController。
  • 外部操作的处理:如果chargeUser是调用外部支付API(而非数据库操作),需要额外考虑支付接口的幂等性与补偿机制——比如支付失败时,确保事务回滚的同时,不会重复扣费;如果支付成功但事务提交失败,需要有重试或对账机制保证最终一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 09:05:16