【发布时间】:2021-09-15 03:15:24
【问题描述】:
我不熟悉 Postgres 并发和锁模型,所以我认为可能有一个解决方案可以解决我遇到的问题,而无需在代码中使用显式锁。 我正在集成一项支付网关服务。每次付款时,我都会收到通知并将总和添加到用户的余额中。 现在问题来了,我收到 2 条具有相同有效负载的消息的可能性很小,但理论上的机会。即单笔交易的两次付款通知。问题是避免在用户余额中添加双倍金额。
代码如下:
var deposit = await _repository.GetDepositByGatewayDepositId(ipn.DepositId);
var oldStatus = default(DepositStatus);
if (deposit == null)
{
deposit = _converter.ToDeposit(model, status, ipn.Id, user.Id);
}
else
{
oldStatus = deposit.Status;
deposit.IpnId = ipn.Id;
deposit.Updated = DateTime.UtcNow;
deposit.Status = status;
}
await _repository.SaveAsync(deposit);
if (deposit.Status == DepositStatus.Completed && oldStatus != DepositStatus.Completed)
{
await _repository.AddBalance(user.Id, ipn.Amounti);
}
仅当先前的付款状态(oldStatus 变量)不是Completed(Pending)时,我才更新用户的余额,但是,我从数据库中获取 oldStatus,并且如果有重复的请求并且第二笔交易是也在进行中,我最终会遇到一个竞争条件。
所以我的问题是,有没有一种有效的方法可以在不使用代码锁的情况下解决问题?也许告诉 Postgres 使用行级锁来阻止其他事务的读取,直到第一个事务完成?
更新: 感谢大家的回复。我想我会为这个主题添加更多细节。当 trans 状态发生变化时,我可能会收到同一笔交易的多条消息(即相同的 ID)。好的部分是消息是数字签名的,所以我已经在签名字段上添加了唯一约束。但是支付网关声明任何状态 >= 100 都是成功状态。所以理论上,如果他们给我发送状态 100,然后是 101,理论上这可能会导致竞争状况和财务后果。同样,机会可能非常低,但我宁愿安全而不是抱歉。
根据回复,最优雅的解决方案似乎是乐观锁定。但是,对于这种特殊情况,我认为分布式锁定可能是更好的选择,因为它似乎更简单。仅为一位特定客户获取锁定,因此客户A 的锁定不会导致客户B 的等待锁定。
谢谢
【问题讨论】:
-
为了避免锁定,您可以使用“乐观锁定”(带有溢出桶和随机重试时间)。它的性能要高得多。或者...您可以序列化交易。或者,您可以在应用程序中使用队列。或者您可以使用显式锁。不过,对于大批量,我仍然更喜欢第一种选择。
标签: .net postgresql concurrency