Postgres 中带有嵌套 SELECT 子句的原子更新

问题描述

我有一个集群,其中一个节点是主节点。通过数据库(通过java/spring/jpa访问Postgres)完成同步/决定谁将成为新的master。

这是我目前拥有的

@Repository
public interface ServiceRepository{


@Transactional
@Modifying
@Query("UPDATE ServiceInstance e SET e.master=true"
        + " where e.id=:serviceId AND NOT"
        + " EXISTS (SELECT master from ServiceInstance where master = true)")
int tryToBecomeMaster(@Param("serviceId") String serviceId);

对于集群中的每个节点,DB 中都有一行 ServiceInstance。每个节点都试图更新自己的行,但前提是没有行已标记为“主”。该查询由每个节点定期运行。

查询原子还是存在竞争条件?如果它不是原子的,我在这里需要什么事务隔离级别?

我主要担心的是,如果允许并行执行 2 个查询,它们可能会将 SELECT 子句计算为 false(无主)并更新它们的行。

解决方法

为了确保只能有一个 master,在表上创建一个唯一的部分索引:

CREATE UNIQUE INDEX ON serviceinstance ((1)) WHERE master;

一旦您尝试将表中多于一行的 master 设置为 TRUE,这将给您一个错误,并且 ACID 保证(一致性)的 C 将确保这总是有效的。您必须在函数中捕获并处理异常。

我要指出,这不是实现高可用性集群的好方法:它具有数据库形式的单点故障。

,

如果你想确保你的 SELECT 子句不能被并行读取,你可以使用带有聚合函数的 SELECT FOR UPDATE 来检测 master 而不是 WHERE 子句;这将自动锁定表的所有行,直到 UPDATE 完成。

另一种方法是手动执行 LOCK TABLE EXCLUSIVE 命令。

两者都应该在您的情况下正常工作。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...