查找下一个不在 table 中的空闲时间戳

Find the next free timestamp not in a table yet

我有一个 table、event,列 unique_time 类型为 timestamptz。我需要 unique_time 中的每个值都是唯一的。

给定 timestamptz 输入 input_time,我需要找到满足以下条件的 最小值 timestamptz 值:

我不能只在unique_time中的最大值上加一微秒,因为我需要满足上述条件的最小值。

作为插入或更新 event table 的一部分,是否有一种简洁的方法来计算它?

啊,忘了我的评论中的方法,这些方法会尝试在 $input_time 之后生成所有微秒时间戳的(无限)序列。有一个更简单的查询可以准确生成您需要的时间戳:

INSERT INTO event(unique_time, others)
SELECT MIN(candidates.time), $other_values
FROM (
  SELECT $input_time AS "time"
UNION ALL
  SELECT unique_time + 1 microsecond AS time
  FROM event
  WHERE unique_time >= $input_time
) AS candidates
WHERE NOT EXISTS (
  SELECT *
  FROM unique_time coll
  WHERE coll.unique_time = candidates.time
);

但是,我不确定 Postgres 对此的优化程度如何,MIN 聚合可能会从 event 加载所有大于 $input_time 的时间戳 - 这可能是如果你总是在最后附加事件,那很好,但仍然如此。一个可能更好的选择是

INSERT INTO event(unique_time, others)
SELECT available.time, $other_values
FROM (
  SELECT *
  FROM (
    SELECT $input_time AS "time"
  UNION ALL
    SELECT unique_time + 1 microsecond AS time
    FROM event
    WHERE unique_time >= $input_time
  ) AS candidates
  WHERE NOT EXISTS (
    SELECT *
    FROM unique_time coll
    WHERE coll.unique_time = candidates.time
  )
  ORDER BY candidates.unique_time ASC
) AS available
ORDER BY available.time ASC
LIMIT 1;

这可能(我不知道)仍然需要在每次插入内容时评估复杂的子查询,如果大多数输入不引起冲突,这将是相当低效的。我也不知道它在并发负载下的效果如何(即多个事务 运行 同时查询)以及它是否有可能的竞争条件。

或者,只需使用 WHILE 循环(在客户端或 PL/SQL 中)尝试插入值,直到成功并在每次迭代时增加时间戳 - 请参阅@Erwin Brandstetter 的回答那。

我建议一个带循环的函数:

CREATE OR REPLACE FUNCTION f_next_free(_input_time timestamptz, OUT _next_free timestamptz)
  LANGUAGE plpgsql STABLE STRICT AS
$func$
BEGIN
   LOOP
      SELECT INTO _next_free  _input_time
      WHERE  NOT EXISTS (SELECT FROM event WHERE unique_time = _input_time);
      
      EXIT WHEN FOUND;
      _input_time := _input_time + interval '1 us';
   END LOOP;
END
$func$;

致电:

SELECT f_next_free('2022-05-17 03:44:22.771741+02');

确保在 event(unique_time) 上有一个索引。如果列定义为 UNIQUEPRIMARY KEY,则该索引隐式存在。

相关:

  • Can I make a plpgsql function return an integer without using a variable?
  • Select rows which are not present in other table
  • BREAK statement in PL/pgSQL

由于 Postgres 时间戳具有微秒分辨率,下一个空闲时间戳至少有 1 微秒 (interval '1 us')。参见:

  • Ignoring time zones altogether in Rails and PostgreSQL

也可以是递归 CTE,但开销可能更大。

并发!

Is there a concise way to compute this as part of an INSERT or UPDATE to the event table?

以上显然受竞争条件的影响。任何数量的并发事务都可能找到相同的空闲点。 Postgres 无法锁定不存在的行。

既然你想INSERT(类似于UPDATE)我建议直接在循环中使用INSERT .. ON CONFLICT DO NOTHING。同样,我们需要 UNIQUEPRIMARY KEY on unique_time:

CREATE OR REPLACE FUNCTION f_next_free(INOUT _input_time timestamptz, _payload text)
  LANGUAGE plpgsql AS
$func$
BEGIN
   LOOP
      INSERT INTO event (unique_time, payload)
      VALUES (_input_time, _payload)
      ON CONFLICT (unique_time) DO NOTHING;
      
      EXIT WHEN FOUND;
      _input_time := _input_time + interval '1 us';
   END LOOP;
END
$func$;

相应地调整您的“有效负载”。

成功 INSERT 锁定行。即使并发事务还看不到插入的行,UNIQUE 索引也是绝对的。
(你 可以 让它与 advisory locks 一起工作......)