禁止“重复键值违反唯一约束”错误

时间:2012-09-12 10:05:56

标签: sql ruby-on-rails postgresql duplicates sql-insert

我正在开发一个使用Postgres作为其数据库的Rails 3应用程序。我有下面的表格:

             Table "public.test"
    Column     |  Type   | Modifiers
---------------+---------+-----------
 id            | integer | not null
 some_other_id | integer |
Indexes:
    "test_pkey" PRIMARY KEY, btree (id)
    "some_other_id_key" UNIQUE CONSTRAINT, btree (some_other_id)

这有两列:

  • id,这是主键(由rails自动创建)
  • some_other_id,包含由另一个系统生成的密钥。这个id必须是唯一的,所以我在表中添加了一个唯一的键约束。

现在,如果我尝试插入一个带有重复some_other_id的行,它会失败(好),我在Postgres日志中得到以下输出:

ERROR:  duplicate key value violates unique constraint "some_other_id_key"

问题在于,我的应用程序尝试添加相同的ID两次是完全主线,并且我的日志被这个“错误”消息发送垃圾邮件,这会导致各种问题:文件占用大量磁盘空间,诊断获取失去了噪音,Postgres不得不丢弃错误以保持日志文件的大小限制等。

有谁知道我怎么做:

  • 通过禁止有关此密钥的所有日志来抑制日志,或者通过在尝试执行INSERT的事务中指定某些内容来抑制日志。
  • 使用其他一些Postgres功能来发现重复的密钥,而不是尝试INSERT。我听说过规则和触发器,但我不能上班(虽然我不是Postgres专家)。

请注意,任何解决方案都需要使用Rails,它执行如下插入:

INSERT INTO test (some_other_id) VALUES (123) RETURNING id;

3 个答案:

答案 0 :(得分:11)

要避免重复键错误开头:

INSERT INTO test (some_other_id)
SELECT 123
WHERE  NOT EXISTS (SELECT 1 FROM test WHERE some_other_id = 123)
RETURNING id;

我假设id是 serial 列,它会自动获取其值。

这受非常小的竞争条件(在SELECTINSERT之间的时间段)。但是可能发生的最糟糕的事情是,你毕竟会遇到重复的密钥错误,这种情况几乎不会发生,在你的情况下不应该成为问题。

如果您的框架限制您使用正确语法的选项,则可以始终使用原始SQL。

或者您可以为此目的创建UDF(用户定义的函数):

CREATE FUNCTION f_my_insert(int)
 RETURNS int LANGUAGE SQL AS
$func$
INSERT INTO test (some_other_id)
SELECT $1
WHERE  NOT EXISTS (SELECT 1 FROM test WHERE some_other_id = $1)
RETURNING id;
$func$

呼叫:

SELECT f_my_insert(123);

或者,默认为已存在的id

CREATE FUNCTION f_my_insert(int)
 RETURNS int LANGUAGE plpgsql AS
$func$
BEGIN;

RETURN QUERY
SELECT id FROM test WHERE some_other_id = $1;

IF NOT FOUND THEN
   INSERT INTO test (some_other_id)
   VALUES ($1)
   RETURNING id;
END IF;

END
$func$

同样,这为竞争条件留下了极小的机会。您可以以降低性能为代价来消除这种情况:

答案 1 :(得分:3)

您可以禁用会话(或全局实际)的错误消息记录,但需要超级用户权限:

通过运行:

set log_min_messages=fatal;

在会话(=连接)结束或发出新的set语句以重置值之前,仅记录致命错误。

但由于只允许超级用户更改它,因此它可能不是一个好的解决方案,因为它会要求您的应用程序用户拥有这个特权,这是一个主要的安全问题。

答案 2 :(得分:0)

如果您只是想在psql工作时抑制这些错误,可以执行

SET client_min_messages TO fatal

将持续你的剩余会话。