当前位置: 首页 > 面试题库 >

如何在PostgreSQL中进行UPSERT(MERGE,INSERT…ON DUPLICATE UPDATE)?

宇文鸿振
2023-03-14
问题内容

这里一个非常常见的问题是如何进行upsert,这是MySQL调用的内容,INSERT ... ON DUPLICATE UPDATE并且该标准支持该MERGE操作。

鉴于PostgreSQL不直接支持它(在9.5页之前),您如何做到这一点?考虑以下:

CREATE TABLE testtable (
    id integer PRIMARY KEY,
    somedata text NOT NULL
);

INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');

现在,假设你想“UPSERT”的元组(2, ‘Joe’),(3, ‘Alan’),因此新表的内容是:

(1, 'fred'),
(2, 'Joe'),    -- Changed value of existing tuple
(3, 'Alan')    -- Added new tuple

那就是人们在讨论时谈论的话题upsert。至关重要的是,在存在多个事务处理同一个表的情况下,任何方法都必须是安全的-通过使用显式锁定或以其他方式抵御由此产生的竞争条件。

在插入中,关于PostgreSQL中的重复更新,对该主题进行了广泛的讨论。,但这是关于MySQL语法的替代方法,并且随着时间的推移,它已经增长了很多无关的细节。我正在努力确定答案。

这些技术还可用于“如果不存在则插入,否则不执行任何操作”,即“在重复键忽略时插入…”。


问题答案:

9.5及更高版本:PostgreSQL 9.5及更高版本的支持INSERT ... ON CONFLICT (key) DO UPDATE(和ON CONFLICT (key) DO NOTHING),即upsert

与的比较ON DUPLICATE KEY UPDATE

快速解释。

有关用法,请参见手册,特别是语法图中的conflict_action子句,以及说明性文字。

与下面给出的9.4及更早版本的解决方案不同,此功能可用于多个冲突的行,并且不需要排他锁定或重试循环。

添加功能的提交在这里,关于功能开发的讨论在这里。

如果您使用的是9.5,并且不需要向后兼容,则可以立即停止阅读。

9.4及更高版本:
PostgreSQL没有任何内置UPSERT(或MERGE)功能,面对并发使用要高效地做到这一点非常困难。

本文详细讨论了该问题。

通常,您必须在两个选项之间进行选择:

重试循环中的各个插入/更新操作;或者
锁定表并进行批量合并
个别行重试循环
如果您希望多个连接同时尝试执行插入操作,则在重试循环中使用单个行高位插入是合理的选择。

PostgreSQL文档包含一个有用的过程,可让您在数据库内部循环执行此操作。与大多数幼稚的解决方案不同,它可以防止丢失更新和插入竞争。但是,它将仅在READ COMMITTED模式下工作,并且仅当您在事务中执行唯一操作时才是安全的。如果触发器或辅助唯一键导致唯一违规,则该功能将无法正常工作。

此策略效率很低。只要可行,您都应该将工作排入队列,并按如下所述进行批量追加。

许多尝试解决此问题的方法都没有考虑回滚,因此导致更新不完整。两笔交易相互竞争;他们的成功一个INSERTS; 另一个得到重复的密钥错误,UPDATE而是执行一个。UPDATE等待INSERT回滚或提交的块。当它回滚时,UPDATE条件重新检查会匹配零行,因此即使UPDATE提交实际上并没有完成您期望的更新。您必须检查结果行计数,并在必要时重试。

一些尝试的解决方案也没有考虑SELECT竞争。如果您尝试简单明了的方法:

-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.

BEGIN;

UPDATE testtable
SET somedata = 'blah'
WHERE id = 2;

-- Remember, this is WRONG. Do NOT COPY IT.

INSERT INTO testtable (id, somedata)
SELECT 2, 'blah'
WHERE NOT EXISTS (SELECT 1 FROM testtable WHERE testtable.id = 2);

COMMIT;

那么当两个同时运行时,会出现几种故障模式。一个问题是已经讨论过的更新重新检查问题。另一个是两个都UPDATE同时匹配零行并继续的地方。然后,他们都做EXISTS测试,这恰好之前的INSERT。两者都获得零行,因此都获得INSERT。一个失败,重复密钥错误。

这就是为什么您需要重试循环的原因。您可能会认为,使用聪明的SQL可以防止重复的键错误或更新丢失,但是您不能这样做。您需要检查行计数或处理重复的键错误(取决于所选方法),然后重试。

请不要为此使用您自己的解决方案。像消息队列一样,这可能是错误的。

带锁的批量更新
有时您想做一个批量上载,在这里您有一个新的数据集要合并成一个较旧的现有数据集。这大大超过各行upserts更高效,更应是首选,只要实用。

在这种情况下,通常按照以下过程操作:

CREATE一张TEMPORARY桌子

COPY 或将新数据批量插入到临时表中

LOCK目标表IN EXCLUSIVE MODE。这允许其他事务对SELECT表进行更改,但不能对其进行任何更改。

做一个UPDATE … FROM的使用临时表中的值的现有记录;

做一个INSERT不已经在目标表中存在的行;

COMMIT,释放锁。

例如,对于问题中给出的示例,使用多值INSERT来填充临时表:

BEGIN;

CREATE TEMPORARY TABLE newvals(id integer, somedata text);

INSERT INTO newvals(id, somedata) VALUES (2, 'Joe'), (3, 'Alan');

LOCK TABLE testtable IN EXCLUSIVE MODE;

UPDATE testtable
SET somedata = newvals.somedata
FROM newvals
WHERE newvals.id = testtable.id;

INSERT INTO testtable
SELECT newvals.id, newvals.somedata
FROM newvals
LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
WHERE testtable.id IS NULL;

COMMIT;


 类似资料:
  • 问题内容: PostgreSQL 9.5的upsert的正确语法,下面的查询显示错误,为什么? 我尝试更改为 然后显示错误,但是 我不想将gallery_id或category_id设置为唯一, 因为我想确保两列都相同然后进行更新.... 如何正确地在postgres 9.5中进行upsert? 如果需要唯一列,我应该使用其他方法,怎么办? 我想确保多列都冲突然后进行更新,正确的用法是什么 表(c

  • 使用postgresql 9.5,查询显示错误,为什么? 我试着改变

  • 可写CTE被认为是9.5之前UPSERT的一种解决方案,如在PostgreSQL中重复更新时插入所述? 可以使用以下可写CTEs惯用语执行UPSERT,其中包含的信息是作为更新还是插入结束的: 此查询将返回“更新”或“插入”,或者可能(很少)失败,违反了https://dba.stackexchange.com/questions/78510/why-is-cte-open-to-lost-upd

  • 我在可更新的视图系统中有一个 INSERT 规则,我想实现一个 UPSERT,例如: 但是,由于这三列可以有许多不同的组合,我不认为我可以设置一个包含所有列的约束(尽管我可能在SQL逻辑中遗漏了一点理解),因此取消了CONFLIT DO NOTHING部分。 理想的解决方案似乎是使用 EXCEPT,但它仅适用于“插入到 SELECT”语句中。有没有办法使用插入到 SELECT 语句中引用新插入的行

  • 我很困惑 Azure Cosmos DB “UpsertDocumentAsync” C# API 的工作原理。看起来对象已更新(如果首次读取它): 如果我直接创建一个对象: 这创建了一个新对象!!我真的必须在打补丁之前阅读文档吗?? 编辑我知道我也需要添加partitionKey。因此,它不再创建新对象,而是将所有未传递字段设置为NULL。这不是补丁行为!那么,我是否需要通过所有字段? 非常感谢

  • PostgreSQL INSERT INTO 语句用于向表中插入新记录。 我们可以插入一行也可以同时插入多行。 语法 INSERT INTO 语句语法格式如下: column1, column2,...columnN 为表中字段名。 value1, value2, value3,...valueN 为字段对应的值。 在使用 INSERT INTO 语句时,字段列必须和数据值数量相同,且顺序也要对应