SQL - 在INSERT期间依赖于服务器错误

时间:2015-09-05 07:51:55

标签: sql ruby postgresql

我正在使用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约束)。

这是一种常见做法吗?或者有任何警告吗?还有其他选择吗?

2 个答案:

答案 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;