我有一个像这样声明的表:
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循环也不会阻止重复吗?
我当然是在插入时使用默认值。怎么会发生这种情况?什么能解决它?
答案 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,它使用两个易失性函数,并且它正在进行重新检查循环。如果你在目标表上持有一个独占锁,那么它看起来很清醒。 (如果您没有持有独占锁,如果有其他并发插入器,它仍可能因重复键错误而失败。)
这个问题似乎可能出现在测试工具或其他可见的组件之外。