当可能存在可能造成冲突的并发插入时,如何在 postgres 中进行条件插入?

how can I do conditional insert in postgres when there can be concurrent inserts that can create conflict?

我正在尝试编写一个实验框架,用户可以根据 location-idstime.[=14 安排一些实验=]

我的 table 模式看起来像:

TABLE experiment (
    id INT NOT NULL PRIMARY KEY,
    name varchar(20) NOT NULL,
    locationIds varchar[] NOT NULL,
    timeStart timestamp NOT NULL,
    timeEnd timestamp NOT NULL,
    createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
)

有插入操作要完成,条件是位置和时间不应重叠。 我想知道当在位置或时间重叠的地方占用 2 个并发插入时,可以做些什么来避免数据状态不一致,

理想情况下,我希望其中一个插入成功,但我很好,如果两者都失败并且应用程序应该重试。

很少有人接近我试图思考:

方法:

方法-1

  1. 有一个 enable 列来告诉某个条目是否有效 或者不是。
  2. 我插入带有 enable=FALSE

  3. 的实验计划条目
  4. 然后我检查是否有任何其他条目已启用并且是 与当前插入重叠。

  5. 如果有这样的条目那我什么都不做那个实验不行 预定。 Else 我将条目更新为 enable=TRUE.

问题:如果存在并发的冲突插入,那么当两者都清除步骤 3 时,两者都会得到 enable=TRUE

我想过如果我让事务隔离级别是未提交的,那么我也无法区分处理中的和已经处理的enable=TRUE

然后我想,如果我将启用标记为枚举 [IN_PROGRESS, ENABLED, DISABLED] 那么方法将如下所示。

方法-2

  1. 有一个 enable 列,告诉某个条目是否是 [IN_PROGRESS, ENABLED, DISABLED]

  2. 我插入带有 enable=IN_PROGRESS

  3. 的实验计划条目
  4. 然后我检查是否有任何其他条目是 enable=ENABLEDenable=IN_PROGRESS 并与当前插入内容重叠。

  5. IF 有这样的条目然后我更新 enable=DISABLED 那个实验不是 预定。 Else 我将条目更新为 enable=ENABLED.

问题:如果存在并发的冲突插入,那么当两者都清除步骤 3 并获得此类重叠条目时,两者都将获得 enable=DISABLED

如果事务隔离级别是READ-COMMITTED 那么只有当每个步骤都是一个事务,而不是整个过程作为一个事务时,这才有效。 如果事务隔离级别是 READ-UNCOMMITTED 那么这可以作为一个事务处理,DISABLED 状态也可以作为一个 ROLLBACK 步骤处理。

方法 3

使用基于触发器的解决方案,因为我正在使用 POSTGRES,我可以为每个插入操作添加一个触发器,post 在我检查重叠条目的地方插入,如果有 none,那么我将行更新为 enable=TRUE

CREATE OR REPLACE FUNCTION enable_if_unique() 
RETURNS TRIGGER AS $$
BEGIN
    IF (TG_OP = 'INSERT') THEN
    UPDATE experiment
    SET NEW.enable=true
    WHERE (SELECT count(1)
           FROM experiment
           WHERE enable= true AND location_Ids && OLD.location_ids AND (OLD.timeStart, OLD.timeEnd) OVERLAPS (timeStart, timeEnd)
        ) = 0;
    RETURN NEW;
    END IF;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER enable_if_unique_trigger BEFORE INSERT ON experiment FOR EACH ROW EXECUTE PROCEDURE enable_if_unique();

我不确定方法 3,因为我觉得它需要触发器以串行方式为每个插入操作执行操作,以便实际上启用其中一个实验,同时禁用其余的重叠实验。

方法-4

从网上搜索其他可能的解决方案,我看到插入使用 Select 语句和 WHERE 子句帮助添加所需的条件。

INSERT INTO experiment(id, name, locationIds, timeStart, timeEnd) 
SELECT 1, 'exp-1', ARRAY[123,234,345], '2020-03-13 12:00:00' 
WHERE (
       SELECT count(1) 
       FROM EXPERIMENT 
       WHERE enable= true 
             AND 
             location_Ids && OLD.location_ids 
             AND 
             (OLD.timeStart, OLD.timeEnd) OVERLAPS (timeStart, timeEnd)
      ) = 0;

我觉得仍然存在一致性问题的可能性,因为两个并发操作将无法读取检查约束的 SELECT 语句中的每一个。

最终方法:方法 2

我想知道以下事情:

  1. 就可扩展性和高吞吐量而言,哪种方法最好?

  2. 哪种方法真正确保了数据的一致性?

  3. 我本可以使用但在这里错过的任何其他方法!!!

POSTGRES 新手,将欣赏示例或链接

如@a_horse_with_no_name

所述

我们可以使用排除约束:

-- this prevents overlaps in the locationids AND the time range
alter table experiment 
  add constraint no_overlap 
  exclude using gist (locationids with &&, tsrange(timestart, timeend) with &&);