Postgres 合并静默忽略唯一约束冲突

Postgres merge silently ignore unique constraint violation

提问人:Nick 提问时间:11/14/2023 更新时间:11/16/2023 访问量:54

问:

以下引文来自这里:https://pganalyze.com/blog/5mins-postgres-15-merge-vs-insert-on-conflict

他基本上发表的评论是 MERGE 的缺点 并发的处理是,当你并发 INSERT 时,所以在 在执行 MERGE 语句的同时,有 另一个 INSERT 正在进行,那么 MERGE 可能不会注意到这一点。MERGE 将 进入其 INSERT 逻辑,然后它会得到一个唯一的冲突。

我记得,当他最初设计 INSERT ON CONFLICT 时 功能,由于 MVCC 工作方式的这种限制 Postgres,您不能使用其通用语法和 以同样可靠的方式工作的选项。如果你想要通用性 的 MERGE,你必须接受这样一个事实,即你可能会得到独特的 约束冲突(当存在并发插入时)与 INSERT ON CONFLICT,它的设计方式及其投机性 insertions,保证您获得 INSERT 或 UPDATE 和 即使存在并发插入,也是如此。您可能希望 如果需要保证,请选择 INSERT ON CONFLICT。

然后假设我们正在做一个非常大的事情,这将需要很长时间,并且在合并过程中,会发生上面引用中描述的并发插入,导致插入逻辑期间唯一约束冲突。在这种情况下究竟会发生什么?它是否会导致整个查询失败并引发唯一约束异常?mergemerge

或者,如果最终插入逻辑中的一行或多行确实发生了唯一约束冲突,则只有那些具有唯一约束冲突的行才会被静默忽略,而其余的插入将照常进行?

我认为 postgres 的功能不支持这一点(后者 - 默默地忽略唯一约束冲突),但我确实认为这将是对功能的宝贵补充,因为肯定会有用户(我目前)不希望他们的整个合并插入(或更新)失败仅仅因为一行或几行具有唯一的约束冲突。mergemerge

postgresql 并发 唯一约束 sql 合并

评论

2赞 Frank Heikens 11/14/2023
运行测试时发生了什么?
0赞 Nick 11/14/2023
我没有(但如果没有人回答我,那么我可能最终会回答)
1赞 Adrian Klaver 11/15/2023
文档将其拼写为 MERGE:如果重复的操作是 INSERT,这将导致唯一性冲突,而重复的 UPDATE 或 DELETE 将导致基数冲突;后一种行为是 SQL 标准所要求的。
0赞 Nick 11/15/2023
@AdrianKlaver这当然是我非常仔细阅读的东西,这就是为什么我写了“我会认为......”。但我想要一个明确的答案,我想开始讨论引入允许默默忽略而不是破坏整个查询的功能。
1赞 Adrian Klaver 11/15/2023
Postgres 开发人员尽可能地坚持 SQL 标准,所以我怀疑你会在这个提议上走得更远。

答:

0赞 Nick 11/16/2023 #1

我确实使用以下代码运行了一些测试,并且我发现,正如预期的那样,如果在插入命令期间发生唯一违规异常,则此唯一违规异常会冒泡并导致整个查询。mergemerge

这意味着,例如,如果您正在执行长时间运行的批处理操作,则可能会执行 99% 的工作,但是,如果即使是最后一个插入行也会导致唯一的冲突异常,则整个查询将被回滚,并且所有工作都会随之丢失。mergemergemerge

在许多可预见的用例中,我预计这将是非常令人失望的,而且远非理想。在我看来,最好提供简单地忽略导致唯一违规异常的行的能力,而不是破坏整个查询。mergemerge

我认为这个附加功能(忽略行,在插入查询期间导致唯一违规异常)应该添加到sql标准和postgres中。

用于测试的代码

在一个 sql 进程中,运行以下命令:

create table tmp_table (col int);

insert into tmp_table (col)
select *
from generate_series(1, 10000000) as s(i);

CREATE TABLE tmp_table2 (col int unique);

merge into tmp_table2 tt2
using (
  select *
  from tmp_table
) as tt1
on tt2.col = tt1.col
when matched then do nothing
when not matched then
  insert (col)
  values (tt1.col);

在第二个 sql 进程中,在第一个 sql 进程中运行命令后立即运行以下命令:merge

insert into tmp_table2 (col)
values (9000000);