11

我正在开发一个使用 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;
4

3 回答 3

11

为了避免重复键错误开始:

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

我假设 id 是一个自动获取其值 的串行列。

这受制于非常小的竞争条件SELECT(在和之间的时间段内INSERT)。但可能发生的最糟糕的情况是,您毕竟会得到一个重复的密钥错误,这几乎不会发生,并且在您的情况下不应该成为问题。

如果您的框架限制您使用正确语法的选项,您始终可以使用原始 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$

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

于 2012-09-12T12:47:04.420 回答
3

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

通过运行:

set log_min_messages=fatal;

在会话 (=connection) 结束或您发出新set语句来重置值之前,只会记录致命错误。

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

于 2012-09-12T10:18:19.253 回答
0

如果你只是想在工作时抑制这些错误psql,你可以这样做

SET client_min_messages TO fatal

这将持续到您的会话的其余部分。

于 2016-09-28T16:25:48.363 回答