我正在使用 NestJS、Prisma ORM 和 MySQL 在支付网关系统中工作。我在处理并发付款请求时遇到问题。当同时发出多个请求时,系统会从用户数据库中检索相同的可用余额,从而导致付款不正确。
问题: 每个请求都会从可用余额中扣除支付金额。 并发请求获取相同的余额值,导致最终用户余额存在差异。
我尝试过的: 在处理请求时使用 SELECT ... FOR UPDATE 查询锁定用户行。 Prisma 的 $transaction 包装了支付流程并确保原子性。
异步initiatePayout(有效负载:PayoutInitiateRequestDto,请求:IRequest):Promise {
返回等待 this.prisma.$transaction(async (prisma) => {
// 锁定事务的用户行
const 用户 = 等待 prisma.$queryRaw
SELECT * FROM "User" WHERE "id" = ${userId} FOR UPDATE
;
if (!user) {
throw new Error('User not found');
}
const availableBalance = user[0]?.totalPayout;
const payoutAmount = +payload.amount;
// Check if the available balance is sufficient
if (availableBalance < payoutAmount) {
throw new Error('Insufficient balance');
}
// External API call for payout process
const apiResponse = await this.externalApi.payout(payload);
if (apiResponse.success) {
// Deduct the balance after successful payout
await this.user.findOneAndUpdate({
{ id: userId },
{ totalPayout: availableBalance - payoutAmount },
});
}
return apiResponse;
}); }
实施条件更新:
条件更新是一种常见的乐观方式,用于在并发访问时保持数据一致,即并发时的一致性。有关此类案例的更多信息,请参阅此处的问答:用户可以通过发起多次并发提款来同时提款超过钱包余额
注意:下面重构的代码可能在语法上有所不同,因为我遵循 mongoose 进行对象建模。然而,条件更新的逻辑保持不变,请对给定语句中的 prisma 进行必要的语法更正。
请重构以下代码:
if (apiResponse.success) {
// Deduct the balance after successful payout
await this.user.findOneAndUpdate({
{ id: userId },
{ totalPayout: availableBalance - payoutAmount },
});
}
就像这样:
// performing a conditional update to safeguard
// the possible write-write conflict
if (apiResponse.success) {
// Deduct the balance after successful payout
const newDoc = await this.user.findOneAndUpdate({
{ id: userId, totalPayout: { $gte : availableBalance } },
{ totalPayout: availableBalance - payoutAmount },
});
// informing user for the needful action
if (newdoc) {
console.log(`Updation passed`);
} else {
console.log(
`Updation failed - write-write conflict detected`
);
// please provide the code to revert the payout transaction here.
}
}
配备条件更新的重构代码会在更新时检查是否有足够的余额。如果有足够的余额,则可以安全地继续,否则,这意味着发生了竞争条件,其他一些用户会话已经使用了余额。 如果中止更新,必须相应地通知用户,并且已经发生的支付交易也必须恢复。
另请注意,如果有并发会话使用了一些余额,但仍有足够的余额用于当前交易,则可以安全地继续。因此,这种方法不会以悲观的方式工作,即使可能的并发事务也会被阻塞。