列默认功能无法创建唯一值

时间:2014-11-11 04:55:00

标签: postgresql stored-procedures insert unique default

我有一个像这样声明的表:

CREATE TABLE my_table (
    ...
    guid uuid UNIQUE DEFAULT create_guid('my_table')
    ....
);

我有一个计算唯一GUID的函数。

CREATE FUNCTION create_guid(in_table text) 
RETURNS uuid AS $$
DECLARE 
    v_guid uuid;
    v_rows int;
BEGIN
    v_guid := md5(current_timestamp::text||random()::text);
    EXECUTE 'SELECT 1 FROM ' || quote_ident(in_table) ||'  WHERE guid=' || quote_literal(v_guid);
    GET DIAGNOSTICS v_rows = ROW_COUNT;
    WHILE v_rows > 0 LOOP -- can't use FOUND with EXECUTE
        v_guid := md5(current_timestamp::text||random()::text||v_guid::text);
        EXECUTE 'SELECT 1 FROM ' || quote_ident(in_table) ||'  WHERE guid=' || quote_literal(v_guid);
        GET DIAGNOSTICS v_rows = ROW_COUNT;
    END LOOP;
    RETURN v_guid;
END;
$$ LANGUAGE PLPGSQL VOLATILE;

我的生产环境中从未遇到INSERT故障,但在我的测试环境中,我可以相当可靠地获得类似于以下内容的错误:

ERROR: duplicate key value violates unique constraint 'my_table_guid_key'
DETAIL:  Key (guid)=(fed050ad-61c4-d548-3008-2de01301c2fc) already exists.

我意识到current_timestamp可能是一个相同的值,我认为random()也可以,但它应该是不太可能而不是。即使它们是相同的,while循环也不会阻止重复吗?

我当然是在插入时使用默认值。怎么会发生这种情况?什么能解决它?

1 个答案:

答案 0 :(得分:3)

  

我有一个计算唯一GUID的函数

您的问题是,您已经彻底改造了UUID算法生成版本,而且它是一个有缺陷的版本。相反,只是:

CREATE EXTENSION "uuid-ossp";

SELECT uuid_generate_v4();

...产生如下输出:

           uuid_generate_v4           
--------------------------------------
 d3e3a21c-877d-4731-a119-09b25015cb7a
(1 row)

即使生日悖论在游戏中,v4 UUID也不会发生碰撞。 Really。包含时间戳实际上会增加碰撞的可能性,而不是减少它。

在您使用uuid数据类型存储UUID时,它会更快,更紧凑。或者至少将它们以bytea形式存储为big-endian值。除了填充的,格式化的文本表示之外的任何东西都会变得缓慢和浪费。


  

即使它们是相同的,while循环也不会阻止重复吗?

不,因为它是upsert / create-if-not-exists problem的变体,其中两个并发事务可以各自插入相同的值,但两者都不能看到其他值。


我清理了这个功能,使它更具可读性,并且除了非常弱的uuid生成方案之外,没有看到任何明显错误的逻辑,并且它不会在面对并发性。

CREATE FUNCTION create_guid(in_table text) 
RETURNS uuid AS $$
DECLARE 
    v_guid uuid;
    v_matched boolean = 1;
BEGIN
    WHILE v_matched
    LOOP
      v_guid := md5(current_timestamp::text||random()::text);
      EXECUTE format('SELECT 1 FROM %I  WHERE guid = %L', in_table, v_guid)
      INTO v_matched;
    END LOOP;
    RETURN v_guid;
END;
$$ LANGUAGE PLPGSQL VOLATILE;

它被声明为volatile,它使用两个易失性函数,并且它正在进行重新检查循环。如果你在目标表上持有一个独占锁,那么它看起来很清醒。 (如果您没有持有独占锁,如果有其他并发插入器,它仍可能因重复键错误而失败。)

这个问题似乎可能出现在测试工具或其他可见的组件之外。