【问题标题】:Atomic UPDATE with nested SELECT clause in PostgresPostgres 中带有嵌套 SELECT 子句的原子更新
【发布时间】:2021-05-25 20:25:19
【问题描述】:

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

这是我目前拥有的

@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);

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

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

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

【问题讨论】:

  • Postgresql 事务始终符合 ACID。 A 表示原子。是的,是的。

标签: java spring postgresql jpa transaction-isolation


【解决方案1】:

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

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

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

我要说的是,这不是实现高可用性集群的好方法:它在数据库中存在单点故障。

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • SELECT FOR UPDATE 没有多大意义,因为 2 个并发事务不会看到任何主事务,因此 SELECT 将返回(并锁定)0 行,然后继续更新 2 个不同的行跨度>
    • 请注意,我建议在 SELECT FOR UPDATE 而不是 WHERE 中使用聚合函数,以便 LOCK 在所有行上正确发生。
    猜你喜欢
    • 2012-07-16
    • 2019-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-28
    • 1970-01-01
    • 1970-01-01
    • 2013-04-30
    相关资源
    最近更新 更多