无论你采取什么行动(@Denis给了你很多选择),这个重写的INSERT
命令都会快得多:
INSERT INTO foos (foo)
SELECT n.foo
FROM unnest ($data::text[]) AS n(foo)
LEFT JOIN foos o USING (foo)
WHERE o.foo IS NULL
它还为可能的竞争条件留下了更短的时间范围。
事实上,时间框架应该如此之短,以至于只有在高并发负载或巨大数组时才会出现独特的违规行为。
数组中的骗子?
除非,如果您的问题是内置的. 输入数组本身是否有重复项?在这种情况下,事务隔离对您没有帮助。敌人在里面!
考虑这个例子/解决方案:
INSERT INTO foos (foo)
SELECT n.foo
FROM (SELECT DISTINCT foo FROM unnest('{foo,bar,foo,baz}'::text[]) AS foo) n
LEFT JOIN foos o USING (foo)
WHERE o.foo IS NULL
我DISTINCT
在子查询中使用以消除“休眠代理”,即重复项。
人们往往会忘记,骗子可能会出现在导入数据中。
全自动化
此函数是一种永久处理并发的方法。如果 aUNIQUE_VIOLATION
发生,INSERT
则只是重试。新出现的行会自动从新尝试中排除。
它没有解决相反的问题,即一行可能已被同时删除 - 这不会被重新插入。有人可能会争辩说,这个结果是可以的,因为这样的结果是DELETE
同时发生的。如果要防止这种情况,请使用SELECT ... FOR SHARE
来保护行免受并发DELETE
。
CREATE OR REPLACE FUNCTION f_insert_array(_data text[], OUT ins_ct int) AS
$func$
BEGIN
LOOP
BEGIN
INSERT INTO foos (foo)
SELECT n.foo
FROM (SELECT DISTINCT foo FROM unnest(_data) AS foo) n
LEFT JOIN foos o USING (foo)
WHERE o.foo IS NULL;
GET DIAGNOSTICS ins_ct = ROW_COUNT;
RETURN;
EXCEPTION WHEN UNIQUE_VIOLATION THEN -- tag.tag has UNIQUE constraint.
RAISE NOTICE 'It actually happened!'; -- hardly ever happens
END;
END LOOP;
END
$func$
LANGUAGE plpgsql;
我让函数返回插入行的计数,这是完全可选的。
-> SQLfiddle 演示