我正在使用PostgreSQL 9.1。假设我有一个表,其中某些列具有UNIQUE
约束。最简单的例子:
CREATE TABLE test (
value INTEGER NOT NULL UNIQUE
);
现在,在插入一些值时,我必须单独处理大小写,其中要插入的值已经在表中。我有两个选择:
SELECT
以确保值不在表格中,或者:INSERT
并注意服务器可能返回的任何错误。利用PostgreSQL数据库的应用程序是用Ruby编写的。以下是我编写第二个选项的方法:
require 'pg'
db = PG.connect(...)
begin
db.exec('INSERT INTO test VALUES (66)')
rescue PG::UniqueViolation
# ... the values are already in the table
else
# ... the values were brand new
end
db.close
这是我的想法:假设我们先插入SELECT
,然后插入。 SQL引擎必须扫描行并返回任何匹配的元组。如果没有,我们会制作一个INSERT
,可能会进行另一次扫描,看看是否有任何机会不会违反UNIQUE
约束。因此,从理论上讲,第二种方案可以将执行速度提高50%。这是PostgreSQL的实际行为吗?
我们假设在涉及异常本身时没有歧义(例如我们只有一个UNIQUE
约束)。
这是一种常见做法吗?或者有任何警告吗?还有其他选择吗?
答案 0 :(得分:2)
这取决于 - 如果您的应用程序UI通常允许输入重复值,那么强烈建议您在插入之前进行检查。因为任何错误都会使当前事务无效,消耗序列/序列值,填充带有错误消息的日志等。
但是如果您的UI不允许重复,并且只有当某人使用技巧(例如在漏洞研究期间)或非常不可能时才插入重复,那么我允许插入而不先检查。
由于唯一约束强制创建索引,因此此检查不会很慢。但肯定比插入和检查罕见错误稍慢。 Postgres 9.5 would have on conflict do nothing
support,这既快又安全。您要检查插入的行数以检测重复项。
答案 1 :(得分:1)
您之前(并且不应该)必须进行测试;您可以在插入时测试。只需将测试添加为where子句即可。以下插入插入零个或一个元组,取决于是否存在具有相同值的行。 (当然不更慢):
INSERT INTO test (value)
SELECT 55
WHERE NOT EXISTS (
SELECT * FROM test
WHERE value = 55
);
虽然您的错误驱动方法可能从客户端看起来很优雅,但从数据库端来看,这是一个近乎灾难:当前事务被隐含地回滚+所有游标(包括预备语句)已关闭。 (因此:您的应用程序必须重建完整的事务但没有错误并重新开始。)
添加:添加多行时,可以将VALUES()
放入CTE并引用插入查询中的CTE:
WITH vvv(val) AS (
VALUES (11),(22),(33),(44),(55),(66)
)
INSERT INTO test(value)
SELECT val FROM vvv
WHERE NOT EXISTS (
SELECT *
FROM test nx
WHERE nx.value = vvv.val
);
-- SELECT * FROM test;