2016-01-15 61 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