【问题标题】:PostgreSQL row read lockPostgreSQL 行读锁
【发布时间】:2018-09-20 12:42:38
【问题描述】:

假设我有一个名为 Withdrawals (id, amount, user_id, status) 的表。

每当我发起提款时,流程如下:

  • 验证用户是否有足够的余额(计算为收到金额的总和 - 提款金额的总和)
  • 插入包含金额、user_id 和 status='pending' 的行
  • 通过gRPC调用第三方软件发起提现(实际是汇款),等待响应
  • 一旦我们做出肯定的回应,就会立即更新状态为“已完成”的行,如果提款失败,则删除该条目。

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

请求 1

  • 用户余额充足
  • 创建提款(余额 = 0)
  • 更新提现状态

请求 2(约 50 毫秒后)

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

目前,如果特定用户在 x 毫秒内,我们正在使用 redis 锁定提款,以避免这种情况,但这不是最强大的解决方案。由于我们现在正在为企业开发 API,使用我们当前的解决方案,我们将阻止可能同时请求的提款。 有什么方法可以根据 Withdrawals 表的 user_id 锁定并确保随后的插入查询等待?

【问题讨论】:

  • 假设您添加了一些代码并解释了您当前作为输出(或错误)得到的内容,预期的输出结果将帮助人们考虑回答您的问题。问一个没有样本数据(与问题相关的表格和字段)的问题并不是真正值得一看的东西!只是一个提示!

标签: sql node.js postgresql concurrency locking


【解决方案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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-17
    相关资源
    最近更新 更多