Как генерировать уникальные метки времени в PostgreSQL?
Моя идея состоит в том, чтобы реализовать базовые "векторные часы", где временные метки основаны на часах, всегда идут вперед и гарантированно будут уникальными.
Например, в простой таблице:
CREATE TABLE IF NOT EXISTS timestamps (
last_modified TIMESTAMP UNIQUE
);
Я использую триггер, чтобы установить значение метки времени перед вставкой. Это в основном просто уходит в будущее, когда две вставки прибывают одновременно:
CREATE OR REPLACE FUNCTION bump_timestamp()
RETURNS trigger AS $$
DECLARE
previous TIMESTAMP;
current TIMESTAMP;
BEGIN
previous := NULL;
SELECT last_modified INTO previous
FROM timestamps
ORDER BY last_modified DESC LIMIT 1;
current := clock_timestamp();
IF previous IS NOT NULL AND previous >= current THEN
current := previous + INTERVAL '1 milliseconds';
END IF;
NEW.last_modified := current;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS tgr_timestamps_last_modified ON timestamps;
CREATE TRIGGER tgr_timestamps_last_modified
BEFORE INSERT OR UPDATE ON timestamps
FOR EACH ROW EXECUTE PROCEDURE bump_timestamp();
Затем я запускаю огромное количество вставок в двух отдельных клиентах:
DO
$$
BEGIN
FOR i IN 1..100000 LOOP
INSERT INTO timestamps DEFAULT VALUES;
END LOOP;
END;
$$;
Как и ожидалось, я получаю столкновения:
ERROR: duplicate key value violates unique constraint "timestamps_last_modified_key"
État SQL :23505
Détail :Key (last_modified)=(2016-01-15 18:35:22.550367) already exists.
Contexte : SQL statement "INSERT INTO timestamps DEFAULT VALUES"
PL/pgSQL function inline_code_block line 4 at SQL statement
@rach предложил смешать current_clock()
с SEQUENCE
объект, но это, вероятно, будет означать избавление от TIMESTAMP
тип. Хотя я не могу понять, как это решит проблему изоляции...
Есть ли общая схема, чтобы этого избежать?
Спасибо за ваши идеи:)
2 ответа
Мои два цента (по мотивам http://tapoueh.org/blog/2013/03/15-batch-update).
попробуйте добавить следующее перед большим количеством вставок:
LOCK TABLE timestamps IN SHARE MODE;
Официальная документация находится здесь: http://www.postgresql.org/docs/current/static/sql-lock.html
Если у вас есть только один сервер Postgres, как вы сказали, я думаю, что использование timestamp + sequence может решить проблему, потому что последовательность не транзакционна и соответствует порядку вставки. Если у вас есть db shard, то это будет намного сложнее, но, возможно, распределенная последовательность 2-го квадранта в BDR может помочь, но я не думаю, что порядочность будет соблюдаться. Я добавил немного кода ниже, если у вас есть настройки для его тестирования.
CREATE SEQUENCE "timestamps_seq";
-- Let's test first, how to generate id.
SELECT extract(epoch from now())::bigint::text || LPAD(nextval('timestamps_seq')::text, 20, '0') as unique_id ;
unique_id
--------------------------------
145288519200000000000000000010
(1 row)
CREATE TABLE IF NOT EXISTS timestamps (
unique_id TEXT UNIQUE NOT NULL DEFAULT extract(epoch from now())::bigint::text || LPAD(nextval('timestamps_seq')::text, 20, '0')
);
INSERT INTO timestamps DEFAULT VALUES;
INSERT INTO timestamps DEFAULT VALUES;
INSERT INTO timestamps DEFAULT VALUES;
select * from timestamps;
unique_id
--------------------------------
145288556900000000000000000001
145288557000000000000000000002
145288557100000000000000000003
(3 rows)
Дай мне знать, если это работает. Я не администратор баз данных, поэтому, возможно, будет неплохо спросить и на dba.stackexchange.com о возможном побочном эффекте.