如何在PostgreSQL中生成唯一的时间戳?

时间:2016-01-15 18:48:26

标签: postgresql race-condition vector-clock

我的想法是实现一个基本的«矢量时钟»,其中时间戳是基于时钟的,总是前进并保证是唯一的。

例如,在一个简单的表中:

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 suggestedcurrent_clock()SEQUENCE对象混合,但这可能意味着摆脱TIMESTAMP类型。即使我无法弄清楚它是如何解决隔离问题的......

是否有一种常见的模式可以避免这种情况?

感谢您的见解:)

2 个答案:

答案 0 :(得分:0)

如果您只有一个Postgres服务器,我认为使用timestamp + sequence可以解决问题,因为序列是非事务性的并且遵循插入顺序。 如果你有db shard那么它将会复杂得多但是BDR中的2ndquadrant的分布式序列可能会有所帮助,但我不认为它会得到尊重。如果您有设置测试它,我在下面添加了一些代码。

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所以也许最好在dba.stackexchange.com上询问潜在的副作用。

答案 1 :(得分:0)

我的两分钱(灵感来自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