如何在Prisma创建Transaction时同步获取关联Wallet的最新余额?
解决方案:原子性操作保证获取最新余额
要解决两次异步查询导致的余额不一致问题,核心是把「查询钱包余额」和「创建交易记录」做成原子性操作,确保中间不会被其他并发请求打断。下面是几种可行的实现方式:
1. 使用Prisma事务(推荐)
Prisma的事务可以将多个数据库操作包裹在一个原子单元里,要么全部成功,要么全部失败,中间不会有其他操作介入。
示例代码:
async function createTransaction(userId: string, amount: number, type: 'DEPOSIT' | 'WITHDRAW') { return await prisma.$transaction(async (tx) => { // 1. 在事务内查询最新的钱包余额 const wallet = await tx.wallet.findUniqueOrThrow({ where: { userId }, select: { balance: true } }); // 2. 创建交易记录,用刚查到的最新余额作为curBalance const transaction = await tx.transaction.create({ data: { userId, amount, type, curBalance: wallet.balance // 其他业务字段... } }); // 如果需要同步更新钱包余额,可在此添加update操作(同样在事务内) await tx.wallet.update({ where: { userId }, data: { balance: wallet.balance + (type === 'DEPOSIT' ? amount : -amount) } }); return transaction; }); }
关键在于用prisma.$transaction包裹所有操作,确保查询和创建在同一个原子事务中执行,彻底避免时间差导致的余额过期问题。
2. 数据库触发器(底层自动保障)
如果希望完全在数据库层面自动处理,可为transaction表创建触发器,插入记录时自动从wallet表拉取对应用户的最新余额填充curBalance。
以PostgreSQL为例,创建触发器的SQL语句:
-- 先定义触发器函数 CREATE OR REPLACE FUNCTION set_transaction_cur_balance() RETURNS TRIGGER AS $$ BEGIN -- 从wallet表获取最新余额并赋值给新记录的curBalance SELECT balance INTO NEW.curBalance FROM wallet WHERE userId = NEW.userId; RETURN NEW; END; $$ LANGUAGE plpgsql; -- 给transaction表绑定前置插入触发器 CREATE TRIGGER before_insert_transaction BEFORE INSERT ON "transaction" FOR EACH ROW EXECUTE FUNCTION set_transaction_cur_balance();
创建完成后,在Prisma中创建transaction时无需手动设置curBalance,数据库会自动填充最新余额,完全规避应用层的并发风险。
注意:Prisma Schema不会自动同步触发器,需手动在数据库执行上述SQL,或通过Prisma的migrations文件添加这段代码。
3. 结合更新与查询(适用于需同步更新余额的场景)
如果创建交易的同时需要更新钱包余额,可利用Prisma的update操作结合select,在更新余额的同时返回最新值,再用该值创建交易:
async function createTransactionAndUpdateBalance(userId: string, amount: number, type: 'DEPOSIT' | 'WITHDRAW') { // 先更新钱包余额并返回更新后的结果 const updatedWallet = await prisma.wallet.update({ where: { userId }, data: { balance: { increment: type === 'DEPOSIT' ? amount : -amount } }, select: { balance: true } }); // 用更新后的余额创建交易记录 return await prisma.transaction.create({ data: { userId, amount, type, curBalance: updatedWallet.balance } }); }
这种方式虽分两步,但更新操作本身是原子的,返回的余额绝对最新,也能避免时间差问题。若无需更新余额,还是第一种事务方案更合适。
内容的提问来源于stack exchange,提问作者rohit kumar
相关产品推荐
相关产品推荐

