【问题标题】:How to generate unique timestamps in PostgreSQL?如何在 PostgreSQL 中生成唯一的时间戳?
【发布时间】:2016-04-21 10:36:24
【问题描述】:

我的想法是实现一个基本的“矢量时钟”,其中时间戳是基于时钟的,始终向前并保证是唯一的。

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

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 列(时间戳、序列)上使用一个键怎么样?否则你有 V1 UUID。
  • 你为什么不直接使用now()INSERT INTO timestamps now(); 或将该字段的默认值设置为 now() 最后,您不能插入重复的 now() 值,因为它会随着每笔交易而变化。
  • 只需在客户端捕获异常并重试。真的就是这么简单。
  • 你用什么进行同步?为什么时间戳比序列更有用?
  • 除了@jcaron 所说的,任何计算机上的时钟都会漂移,当计算机同步其时钟时,操作系统时间会跳跃。时钟可能会向任何方向漂移,因此可以从未来或过去获取时间戳,即无序。我认为,sequence 是保证唯一性的方法,如果需要,可以添加时间戳,但不要假设它是唯一的。

标签: postgresql race-condition vector-clock


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    如果你说的只有一台 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 上询问潜在的副作用。

    【讨论】:

    • 还可以有 2 列 DATETIME 和 BIGSERIAL 并在两者上都有索引,这样您就可以快速订购了。我假设您希望它们基于时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-23
    • 1970-01-01
    • 2018-07-04
    • 1970-01-01
    • 2021-01-07
    • 1970-01-01
    • 2010-11-15
    相关资源
    最近更新 更多