2016-01-15 31 views
0

我的想法是实现一个基本的“矢量时钟”,其中时间戳是基于时钟的,总是前进并保证是唯一的。如何在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 suggested与一个SEQUENCE对象混合current_clock(),但它可能意味着摆脱的类型。即使我真的不知道如何解决隔离问题...

有没有一种常见的模式来避免这种情况?

感谢您的见解:)

+1

出了什么问题只是一个序列?你真的需要时间吗?在2列(时间戳,序列)上使用密钥怎么办?否则你有V1 UUID。 – jcaron

+0

为什么不使用'now()'? 'INSERT INTO timestamps now();'或者将该字段的默认值设置为now(),最终不能插入重复的now()值,因为它随每个事务而改变。 – Solrac

+0

@SolracRagnarockradio,同一事务中的任何多次插入都会得到相同的时间戳。也不确定时间戳的微秒准确性会保证不同的值。 – jcaron

回答

0

如果像你说的只有一个Postgres的服务器,我认为这是因为序列是非事务,尊重插入顺序使用时间戳+序列就可以解决问题。 如果你有db分片,那么它会复杂得多,但是可能在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上询问潜在的副作用。

+0

也可以有2列DATETIME和BIGSERIAL两个索引,所以你可以快速订购..我假设你为什么要他们基于时间。 – Rach