PostgreSQL行读取锁

时间:2018-09-20 12:42:38

标签: sql node.js postgresql concurrency locking

假设我有一个名为取款的表格(id,金额,user_id,状态)。

每当我开始提款时,流程如下:

  • 验证用户是否有足够的余额(以收到的金额之和-取款金额之和计算)
  • 插入包含金额,user_id和status =“待处理”的行
  • 通过gRPC调用第三方软件以发起提款(实际上是汇款),等待响应
  • 一旦我们收到肯定答复,立即更新状态为“已完成”的行;如果提款失败,则删除条目。

但是,我在此流程中存在并发问题。 假设用户在相差约50毫秒内发出了2次全额提款请求:

请求1

  • 用户有足够的余额
  • 创建提款(余额= 0)
  • 更新提款状态

请求2(约50毫秒后)

  • 用户有足够的余额(这是不正确的,尚未存储其他插入内容)
  • 创建提款(余额=负数)
  • 更新提款状态

现在,我们正在使用Redis将提款锁定到特定用户(如果提款在x ms以内),以避免这种情况,但这不是最可靠的解决方案。在我们目前使用当前解决方案开发针对企业的API的过程中,我们将阻止可能同时提出的提款请求。 有什么方法可以锁定并确保随后的插入查询根据取款表的user_id等待吗?

1 个答案:

答案 0 :(得分:1)

这是事务隔离的属性。关于它的文章很多,我强烈推荐Designing Data-Intensive Applications中的概述。我发现它是增进个人理解的最有用的描述。

默认的postgres级别为READ COMMITTED,这使这些并发事务中的每一个都可以看到相似的(可用资金状态),即使它们应该是依赖的。

一种解决方法是将每笔交易标记为"SERIALIZABLE" consistency.

  

SERIALIZABLE当前事务的所有语句只能看到   在第一个查询或数据修改语句之前提交的行   已在此交易中执行。如果是读写模式   并发可序列化事务之间会造成一种情况   任何串行(一次)执行都不可能发生   在这些交易中,其中一项将回滚   serialization_failure错误。

这应该以可用性为代价来增强应用程序的正确性,即在这种情况下,第二笔交易将不允许修改记录,并且将被拒绝,这需要重试。对于POC或低流量应用程序,这通常是一个完全可以接受的第一步,因为您可以立即确保正确性。


在以上引用的书中,我还认为存在一个有关ATM句柄可用性的示例。他们考虑到了这种竞争状况,如果用户无法连接到集中式银行,而是限制了最大提款额以最小化爆炸半径,则用户会透支!


解决此问题的另一种体系结构方法是使事务脱机并使它们异步,从而将每个用户调用的事务发布到队列中,然后通过让队列的单个使用者自然避免任何竞争条件。这里的权衡类似,只有一个工作人员可以提供固定的吞吐量,但这确实有助于解决当前的正确性问题:P


跨机器的锁定(例如在postgres / grpc中使用redis)称为分布式锁定,https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

相关问题