9.5及更高版本:
PostgreSQL 9.5和更高版本的支持INSERT ... ON CONFLICT UPDATE
(和ON CONFLICT DO NOTHING
),即upsert。
与的比较ON DUPLICATE KEY UPDATE
。
快速解释。
有关用法,请参见手册,特别是语法图中的conflict_action子句和说明性文字。
与下面给出的9.4及更早版本的解决方案不同,此功能可用于多个冲突的行,并且不需要排他锁定或重试循环。
添加功能的提交在这里,关于功能开发的讨论在这里。
如果您使用的是9.5,并且不需要向后兼容,则可以立即停止阅读。
9.4及更高版本:
PostgreSQL没有任何内置UPSERT
(或MERGE
)功能,面对并发使用要高效地执行它非常困难。
本文详细讨论了该问题。
通常,您必须在两个选项之间进行选择:
- 重试循环中的各个插入/更新操作;要么
- 锁定表并进行批量合并
个别行重试循环
如果您希望多个连接同时尝试执行插入操作,则在重试循环中使用单个行向上插入是合理的选择。
PostgreSQL文档包含一个有用的过程,可让您在数据库内部循环执行此操作。与大多数幼稚的解决方案不同,它可以防止丢失更新和插入竞争。但是,它将仅在READ COMMITTED
模式下工作,并且只有在事务中唯一执行时,它才是安全的。如果触发器或辅助唯一键导致唯一违规,则该功能将无法正常工作。
此策略效率很低。只要可行,您都应该将工作排入队列,并按如下所述进行批量追加。
许多尝试解决此问题的方法都没有考虑回滚,因此导致更新不完整。两笔交易相互竞争;他们的成功一个INSERT
S; 另一个得到重复的密钥错误,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更高效,更应是首选,只要实用。
在这种情况下,通常需要执行以下过程:
例如,对于问题中给出的示例,使用多值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;
相关阅读
那MERGE
呢
SQL标准MERGE
实际上具有定义不明确的并发语义,因此不先锁定表就不适合进行上载。
对于数据合并,这是一个非常有用的OLAP语句,但对于并发安全的ups实际上,它并不是有用的解决方案。对于使用其他DBMS进行更新的人们有很多建议MERGE
,但这实际上是错误的。
其他数据库: