代码之家  ›  专栏  ›  技术社区  ›  Christian Ivicevic

如何防止PostgreSQL中多个客户端同时运行非原子计算的更新异常?

  •  0
  • Christian Ivicevic  · 技术社区  · 7 年前

    我正在运行三个PostgreSQL实例,使用复制(1个主服务器,2个从服务器),这是由两个独立的服务器访问的:

    • 第一个(未公开的)服务器基本上遍历特定表中的每一行,并连续更新每个用户的每一个刻度(基于这些资源的生产速率)的特定列(资源)。
    • 第二个服务器是一个公开各种功能的公共API,比如花费一定数量的资源。

    为了访问和操作数据,我使用了一个ORM库,它允许我编写如下代码:

    const resources = await repository.findById(1337);
    // some complex computation
    resources.iron = computeNewIron(resources.iron);
    await repository.save(resources);
    

    当然,当处理ticks的服务器试图更新资源量时,API可能会希望扣除特定数量的资源,这可能会导致任何一个服务器占用一定数量的资源,这是不正确的,基本上是典型的更新异常。

    我的问题是,我不仅仅是编写一个“简单”的原子查询 UPDATE table SET iron = iron + 42 WHERE id = :id . ORM库在内部使用的是一个直接赋值,它不自引用产生类似于 UPDATE table SET iron = 123 WHERE id = :id 以前计算过的金额。

    我可以假设,如果使用手动编写的查询,使用自引用原子地递增/递减值,则可以防止出现上述异常。我想知道还有哪些选择可以缓解这个问题。我应该在事务中包装我的SELECT/computation/UPDATE吗?够了吗?

    1 回复  |  直到 7 年前
        1
  •  1
  •   Laurenz Albe    7 年前

    您的问题有点不清楚,但如果事务跨越多个语句,但需要保持数据库的一致状态,则基本上有两个选项:

    1. 使用悲观锁定:从数据库中读取值时,使用 SELECT ... FOR UPDATE . 然后,行在事务期间被锁定,并且任何并发事务都不能修改它们。

    2. 使用乐观锁定:在中启动事务 REPEATABLE READ 隔离级别。然后在事务的整个持续时间内看到数据库的一致快照。如果有人在你阅读数据后修改了你的数据,你的 UPDATE 会导致 序列化错误 你必须重新尝试交易。

    如果冲突很少,乐观锁定更好,而如果冲突很可能发生,则悲观锁定更可取。