查找下一个不在 table 中的空闲时间戳
Find the next free timestamp not in a table yet
我有一个 table、event
,列 unique_time
类型为 timestamptz
。我需要 unique_time
中的每个值都是唯一的。
给定 timestamptz
输入 input_time
,我需要找到满足以下条件的 最小值 timestamptz
值:
- 结果必须>=
input_time
- 结果不能已经在
unique_time
我不能只在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)
上有一个索引。如果列定义为 UNIQUE
或 PRIMARY 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
。同样,我们需要 UNIQUE
或 PRIMARY 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 一起工作......)
我有一个 table、event
,列 unique_time
类型为 timestamptz
。我需要 unique_time
中的每个值都是唯一的。
给定 timestamptz
输入 input_time
,我需要找到满足以下条件的 最小值 timestamptz
值:
- 结果必须>=
input_time
- 结果不能已经在
unique_time
我不能只在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)
上有一个索引。如果列定义为 UNIQUE
或 PRIMARY 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
orUPDATE
to theevent
table?
以上显然受竞争条件的影响。任何数量的并发事务都可能找到相同的空闲点。 Postgres 无法锁定不存在的行。
既然你想INSERT
(类似于UPDATE
)我建议直接在循环中使用INSERT .. ON CONFLICT DO NOTHING
。同样,我们需要 UNIQUE
或 PRIMARY 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 一起工作......)