回答

收藏

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

技术问答 技术问答 188 人阅读 | 0 人回复 | 2023-09-14

这里一个非常常见的问题是如何进行upsert,这是MySQL调用内容,INSERT ... ON DUPLICATE UPDATE该标准支持该标准MERGE操作。3 v( @5 T9 N" W( D
鉴于PostgreSQL9..5页前),你是怎么做到的?考虑以下几点:
+ d2 T8 G" W" a- e' s2 z& lCREATE TABLE testtable (    id integer PRIMARY KEY,   somedata text NOT NULL);INSERT INTO testtable (id,somedata) VALUES(1,'fred(二),bob');假设你现在想UPSERT元组(2,‘Joe’),(3,‘Alan’),因此,新表的内容是:
! q/ x) C2 m- T# K: Z(1,'fred(二),Joe  -- Changed value of existing tuple(3,'Alan    -- Added new tuple也就是说,人们在讨论论的话题upsert。至关重要的是,在多个事务处理相同表格时,任何方法都必须安全-通过使用显式锁定或以其他方式抵此产生的竞争条件。
2 j8 k! M5 W& o4 m7 H( g& C0 q关于插入PostgreSQL重复更新,对主题进行了广泛的讨论。,但这是关于MySQL随着时间的推移,语法的替代方法增加了许多无关的细节。我正在努力确定答案。+ c* o; N3 K6 o
这些技术也可用于如果没有,则插入,否则不执行任何操作…”。4 S8 X% t, |# y! K( \; V, X5 w
                                                               
& K! C  w! i$ j, M  `8 }% h    解决方案:                                                                ; G$ B5 \: x2 Y* u1 b
                                                                9.5及更高版本:PostgreSQL 9.支持更高版本INSERT ... ON CONFLICT (key) DO UPDATE(和ON CONFLICT (key) DO NOTHING),即upsert。
8 s  r3 @- P- V+ w: j, k& Y与的比较ON DUPLICATE KEY UPDATE。
6 |( v- N. ^/ {; \! K9 r快速解释。3 k8 [- b8 g7 h
请参阅手册,特别是语法图中的相关用法conflict_action以及解释性文字。9 n" `# n& [9 z) q. I
以下9.4.早期版本的解决方案不同。该功能可用于多个冲突行,无需排他锁定或重试循环。" p% x9 `' _. \$ L) x0 f
这里提交添加功能,这里讨论功能开发。
- ^- w& b: R6 }假如你用9.5.如果不需要向后兼容,可以立即停止阅读。- |; `/ K: a1 a% H2 _
9.4及更高版本:5 X$ {+ V1 @7 h' E9 D
PostgreSQL没有内置UPSERT(或MERGE)很难有效地面对并发使用。& `! |( |& l5 S
本文详细讨论了这个问题。6 t$ X# n$ D6 \$ S& D& L
通常,您必须在两个选项之间进行选择:
0 }" s* g" w) L% z' G8 n6 {& N重试循环中的每个插入/更新操作;或
2 n/ p3 r8 P+ I4 u锁定表并分批合并; j2 {+ p! k( I2 @* y  }
个别行重试循环
- }; K# B# j2 f+ h$ s4 @如果您想同时尝试插入多个连接,则在重试循环中使用单个行高插入是合理的选择。
6 _2 |$ k* L/ ?4 Y* A0 @PostgreSQL文档包含一个有用的过程,允许您在数据库内循环此操作。与大多数幼稚的解决方案不同,它可以防止丢失更新和插入竞争。然而,它是READ COMMITTED在模式下工作,只有在事务中执行唯一操作时才是安全的。如果触发器或辅助唯一键导致唯一违规,该功能将无法正常工作。
) x# Y) k: w3 K. O# I这个策略效率很低。只要可行,就要把工作排在队列里,按照下面的规定批量加。
: p- \( @& i* a8 D2 Q. z很多尝试解决这个问题的方法都没有考虑回滚,导致更新不完整。两笔交易相互竞争;他们的成功之一INSERTS; 另一个重复的密钥错误,UPDATE而是执行一个。UPDATE等待INSERT回滚或提交块。回滚时,UPDATE重新检查条件会匹配零行,所以即使是UPDATE事实上,提交并没有完成您期望的更新。必要时必须检查结果的行计数并重试。: Y- }' N" u' I, g
没有考虑一些尝试解决方案SELECT竞争。如果你尝试简单明了的方法:
  n" J0 d6 y. V. D# }  m, N0 L-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.BEGIN;UPDATE testtableSET 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。一个失败,重复密钥错误。
$ `3 e2 w4 X0 {这就是为什么你需要重试循环。你可能会认为聪明的使用SQL它可以防止重复的键错误或更新丢失,但你不能这样做。您需要检查行数或处理重复的键错误(取决于所选方法),然后重试。
+ p2 n% A5 B9 |- A请不要为此使用自己的解决方案。就像消息队列一样,这可能是错误的。
+ n  @) D& h& n( Z7 c# p批量更新带锁/ l) v2 {% d, k: p
有时候你想批量上载,这里你有一个新的数据集合并成旧的现有数据集。这远远超过了各行各业upserts只要实用,更高效、更应该是首选。* {, x# w- F0 M- Y9 G7 N6 J
在这种情况下,通常按照以下流程进行操作:/ `0 H! T+ E7 K  \, z6 n
CREATE一张TEMPORARY桌子8 |9 N" b: ~1 n" d& {$ Z/ M& Q
COPY 或将新数据批量插入临时表
+ E% f" i* b! i) |) S$ NLOCK目标表IN EXCLUSIVE MODE。这允许其他事务对SELECT更改表格,但不能更改。
4 {4 J  m7 x! w) w6 I. f' c$ c8 Z做一个UPDATE … FROM临时表中使用值的现有记录;
1 J2 }3 E( I/ N$ c做一个INSERT目标表中已经存在的行;
/ d" f: M  u8 U5 @+ B) |COMMIT,释放锁。
, y5 r8 ]% Z# x- L7 p6 h# @8 ^例如,对于问题中给出的示例,使用多值INSERT填写临时表:
' f. [# ]( b2 `# PBEGIN;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 testtableSET somedata = newvals.somedataFROM newvalsWHERE newvals.id = testtable.id;INSERT INTO testtableSELECT newvals.id,newvals.somedataFROM newvalsLEFT OUTER JOIN testtable ON (testtable.id = newvals.id)WHERE testtable.id IS NULL;COMMIT;
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则