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

时间:2019-02-11 00:41:56

标签: database postgresql transactions concurrentmodification

我正在使用复制(三个主服务器,两个从属服务器)运行三个PostgreSQL实例,这两个实例可通过两个单独的服务器进行访问:

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

为了访问和处理数据,我使用了ORM库,该库允许我编写如下代码:

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

当然,当处理价格变动的服务器尝试更新资源量时,API可能会想扣除一定数量的资源,这可能会导致其中任何一台服务器承担一定数量的资源。错误,基本上是典型的UPDATE异常。

我的问题是我不仅在编写诸如UPDATE table SET iron = iron + 42 WHERE id = :id之类的“简单”原子查询。 ORM库在内部使用的是直接分配,该分配不会自引用各个列,这会产生类似于UPDATE table SET iron = 123 WHERE id = :id的情况,其中先前已计算出该数量。

如果我使用手动编写的查询来自动地使用自引用对值进行递增/递减,那么我可以假设可以防止上述异常。我想知道哪些其他选项可以缓解此问题。我应该在事务中包装SELECT / COMPUTE / UPDATE吗?这样就够了吗?

1 个答案:

答案 0 :(得分:1)

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

  1. 使用悲观锁定:从数据库中读取值时,请使用SELECT ... FOR UPDATE。然后,在事务期间将锁定行,并且没有任何并发​​事务可以修改它们。

  2. 使用开放式锁定:以REPEATABLE READ隔离级别启动事务。然后,您会在整个事务期间看到数据库的一致快照。如果其他人在您读取数据后修改了您的数据,则您的UPDATE将导致序列化错误,您将不得不重试该事务。

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