如何使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
相关产品推荐
相关产品推荐

