【问题标题】:Prevent concurrent update on Postgresql database row防止对 Postgresql 数据库行的并发更新
【发布时间】:2018-03-23 10:48:32
【问题描述】:

我有一个类似下面的查询,用于在通过特定网关发生每笔交易后增加交易计数。以下查询用于执行事务维护作业的 PHP 应用程序。如何确保两个并发更新不会造成计数问题?

考虑使用 PostgreSQL 的咨询锁,但不确定如何在以下上下文中使用它。 Java 中的引用很少,但在 PHP 中相对较新。

查询1(查找要更新的记录):

SELECT id
FROM Client.gatewaystat_tbl
WHERE clientid = 1 AND gatewayid = 2 ;

如果找到记录:

UPDATE gatewaystat_tbl gt
SET statvalue = statvalue + 1
WHERE id = '<from above query>'

如果没有找到记录:

INSERT INTO client.gatewaystat_tbl
   (gatewayid, clientid, statetypeid, statvalue)
VALUES (2, 1, 1, 1);

上面的代码可能在一秒钟内被多次调用,因为在高峰时段交易量会非常大,每次交易后计数器 (statvalue) 需要增加。如何确保多个线程不会同时尝试更新计数并最终得到错误数据?

【问题讨论】:

    标签: php postgresql concurrency


    【解决方案1】:

    statvalue 没有更新可能会丢失,因为 UPDATE 是原子的。该行将在语句之前被锁定,并且只有在事务提交时才会释放锁定。

    虽然您不会丢失对 statvalue 的更新,但可能会发生其他问题:

    1. 并发事务可以更新或删除SELECTUPDATE 之间的行。

    2. 并发事务可能会在 SELECTINSERT 之间插入冲突行。

    您可以通过使用将锁定行的SELECT ... FOR UPDATE 来保护自己免受第一个问题的影响,但您必须准备好在INSERT 期间处理错误。

    如果您对(gatewayid, clientid) 有一个主键或唯一约束,您可以在一条语句中完成所有这些操作并避免所有这些问题:

    INSERT INTO client.gatewaystat_tbl
       (gatewayid, clientid, statetypeid, statvalue)
    VALUES (2, 1, 1, 1)
    ON CONFLICT (gatewayid, clientid) DO UPDATE
       SET statvalue = statvalue + 1;
    

    【讨论】:

    • 单个语句也会是原子的吗?它会首先尝试插入并且在发生冲突时会更新。听到了什么,因为插入将每月仅发生一次,并且将在每个月的第一天重置(通过将“启用”列值设置为“false”来禁用上个月的行)。
    • 我相信开销并不可怕(更新与插入非常相似),但您应该确定它的基准。
    • 此功能仅在 Postgres 9.5 以上版本可用。我们正在使用 Postgres 9.3 和 9.4 是一些环境。
    • 那么你不能使用它并且必须通过处理INSERTUPDATEs期间没有改变行的错误来处理潜在的竞争条件。或者你升级 - 9.3 将在今年秋天停止支持。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多