PostgreSQL - 继续 unique_violation (plpgsql)

Posted

技术标签:

【中文标题】PostgreSQL - 继续 unique_violation (plpgsql)【英文标题】:PostgreSQL - Continue on unique_violation (plpgsql) 【发布时间】:2012-07-25 23:03:25 【问题描述】:

我有一个 PostgreSQL 表,其中有一些索引字段,并且这些字段必须是唯一的,以防止重复。这要归功于插入所有字段并捕获 unique_violation 异常的 PLPGSQL 函数,尽管即使只有一个重复记录,它也会停止插入记录。

由于性能问题,我无法进行多次 INSERT(其中一些以数百次完成),问题是即使只有一个重复,它也会停止所有过程,例如以下示例中最火的两个值.

CREATE OR REPLACE FUNCTION easy_import() RETURNS VOID AS
  $$
  BEGIN
    BEGIN
      INSERT INTO things ("title", "uniq1", "uniq2") VALUES
      ('title 1', 100, 102),
      ('title 2', 100, 102),
      ('title 3', 101, 102),
      ('title 4', 102, 102),
      ('title 5', 103, 102),
      ('title 6', 104, 102),
      ('title 7', 105, 102),
      ('title 8', 106, 102),
      ('title 9', 107, 102),
      ('title 10', 108, 102);
      RETURN;
    EXCEPTION WHEN unique_violation THEN
      -- do nothing
    END;
  END;
  $$
  LANGUAGE plpgsql;

有没有办法只为一条记录忽略 unique_violation 并阻止它停止进一步的 INSERT?

谢谢。

更新

唯一索引在“uniq1”和“uniq2”字段中显示,很抱歉造成混淆。 虽然@cdhowie 的解决方案似乎是最好的,但它以某种方式忽略了这样一个事实,即如果您运行相同的查询,它将触发错误。这很奇怪,因为查询执行JOIN 是有原因的。仍在努力。

【问题讨论】:

这里的唯一约束是什么? (uniq1, uniq2)? @cdhowie 是的,这些是我创建唯一索引的字段。 【参考方案1】:

假设唯一约束是围绕 uniq1 和 uniq2 复合的,这将起作用:

INSERT INTO things

WITH new_rows (title, uniq1, uniq2) AS (VALUES
    ('title 1', 100, 102),
    ('title 2', 100, 102),
    ('title 3', 101, 102),
    ('title 4', 102, 102),
    ('title 5', 103, 102),
    ('title 6', 104, 102),
    ('title 7', 105, 102),
    ('title 8', 106, 102),
    ('title 9', 107, 102),
    ('title 10', 108, 102)
)

SELECT
    DISTINCT ON (n.uniq1, n.uniq2)
    n.title, n.uniq1, n.uniq2

FROM new_rows AS n

LEFT JOIN things AS t
ON n.uniq1 = t.uniq1 AND n.uniq2 = t.uniq2

WHERE t.uniq1 IS NULL;

这实际上可能最终不如单个 INSERT 语句的性能,但它是唯一可以解决问题的其他事情。对每种方法进行基准测试,看看哪种方法最适合您。

【讨论】:

【参考方案2】:

你的桌子是这样的:

CREATE TABLE t
(
  title text,
  uniq1 int not null,
  uniq2 int nut null,
  CONSTRAINT t_pk_u1_u2 PRIMARY KEY (uniq1,uniq2)
)

所以让我添加一条规则:

CREATE OR REPLACE RULE ignore_duplicate_inserts_on_t AS ON INSERT TO t
   WHERE (EXISTS ( SELECT 1 FROM t WHERE t.uniq1 = new.uniq1 and t.uniq2 = new.uniq2))
   DO INSTEAD NOTHING;

然后,您可以运行以下查询:

insert into t(title,uniq1,uniq2) values 
    ('title 1', 100, 102),
    ('title 2', 100, 102),
    ...;

如果您的桌子很大,这种方式是最佳选择。我在大约 200 万行的表上进行了测试(针对这种方式和上面 cdhowie 先生提到的连接方式),结果是:

Rule way (mentioned in this comment): 1400 rows per second
Join way (mentioned in above comment): 650 rows per second

【讨论】:

【参考方案3】:

不。

BEGIN ... EXCEPTION 块是子事务,当块内的语句失败时,子事务会回滚。要么全有,要么全无。

使用 cdhowie 的方法 - 在 VALUES 上添加 JOIN

【讨论】:

【参考方案4】:

要获得您正在寻找的行为,只需将 INSERT 语句拆分为单独的语句,而不是在一个语句中包含多个值。与您的原始版本相比,性能应该没有任何明显的差异,因为一切仍然在一个事务中。

CREATE OR REPLACE FUNCTION easy_import() RETURNS VOID AS
$$
BEGIN
BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 1', 100, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 2', 100, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 3', 101, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 4', 102, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 5', 103, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 6', 104, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 7', 105, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 8', 106, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 9', 107, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

BEGIN
  INSERT INTO things ("title", "uniq1", "uniq2") VALUES ('title 10', 108, 102);
EXCEPTION WHEN unique_violation THEN
 -- do nothing
END;

RETURN;
END;
$$
LANGUAGE plpgsql;

【讨论】:

你确定吗?我将对其进行测试,但在我开始使用 PLPGSQL 函数之前,我有单独的插入并且它们很慢。 正如 Craig 指出的那样,BEGIN 块创建了一个子事务,这会使事情变慢一点。 WITH 方法的缺点是您假设表是空的。如果表中已经有一行违反了插入的任何行的唯一性,则整个语句将失败,而子事务方法不会。

以上是关于PostgreSQL - 继续 unique_violation (plpgsql)的主要内容,如果未能解决你的问题,请参考以下文章

发生异常后,如何使用 PostgreSQL 在 Spring Boot 中继续事务?

postgresql编译安装与调试

PostgreSQL:块中的无效页眉

年度DBMS:PostgreSQL蝉联2018年度冠军

SonarQube 8.5 的 PostgreSQL 连接失败

ORACLE ---- 和PostgreSQL继承表类似的实现方式