回答

收藏

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

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

这里一个非常常见的问题是如何进行upsert,这是MySQL调用内容,INSERT ... ON DUPLICATE UPDATE该标准支持该标准MERGE操作。. `- Z0 r' E8 d5 m$ `
鉴于PostgreSQL9..5页前),你是怎么做到的?考虑以下几点:9 \# S& ?% X7 t+ I7 ~  r* T+ T
CREATE TABLE testtable (    id integer PRIMARY KEY,   somedata text NOT NULL);INSERT INTO testtable (id,somedata) VALUES(1,'fred(二),bob');假设你现在想UPSERT元组(2,‘Joe’),(3,‘Alan’),因此,新表的内容是:/ m$ P' V. q' U, N  ~' M( L
(1,'fred(二),Joe  -- Changed value of existing tuple(3,'Alan    -- Added new tuple也就是说,人们在讨论论的话题upsert。至关重要的是,在多个事务处理相同表格时,任何方法都必须安全-通过使用显式锁定或以其他方式抵此产生的竞争条件。
* m4 ~' Z2 w! W8 ~( k' F关于插入PostgreSQL重复更新,对主题进行了广泛的讨论。,但这是关于MySQL随着时间的推移,语法的替代方法增加了许多无关的细节。我正在努力确定答案。
" [, c& Z1 e: Y这些技术也可用于如果没有,则插入,否则不执行任何操作…”。# G1 y5 u1 ?- y$ v5 o5 r
                                                               
1 I/ ~. y# R2 \' \% l1 ~$ F    解决方案:                                                               
- {0 h/ [) S" I, n$ L( @                                                                9.5及更高版本:PostgreSQL 9.支持更高版本INSERT ... ON CONFLICT (key) DO UPDATE(和ON CONFLICT (key) DO NOTHING),即upsert。
- s9 S" S$ S( H8 \$ \与的比较ON DUPLICATE KEY UPDATE。
4 E0 d  I* e" g3 {快速解释。
5 s" |8 k$ ]  {( ?1 B, v3 a1 y6 @请参阅手册,特别是语法图中的相关用法conflict_action以及解释性文字。- A+ r6 U8 m# J3 ?7 D3 F
以下9.4.早期版本的解决方案不同。该功能可用于多个冲突行,无需排他锁定或重试循环。
5 h  s- Z4 k* ~+ N3 J0 u0 c这里提交添加功能,这里讨论功能开发。3 M. I* {7 Q+ k. _# I: [
假如你用9.5.如果不需要向后兼容,可以立即停止阅读。: y" U- J, n0 L/ m+ }* n
9.4及更高版本:- q: `6 y, X: l
PostgreSQL没有内置UPSERT(或MERGE)很难有效地面对并发使用。
* x8 L/ F( M5 e; O4 q本文详细讨论了这个问题。
* X# P4 u# H# Q通常,您必须在两个选项之间进行选择:( e$ r# |5 m% q: |1 y5 y% N) q
重试循环中的每个插入/更新操作;或
" _$ S2 l" M* P: X6 Q  a* y锁定表并分批合并1 t5 S: [( {2 v9 J  Z" g
个别行重试循环
7 b; W' B# \+ W! B2 R; a% t如果您想同时尝试插入多个连接,则在重试循环中使用单个行高插入是合理的选择。2 T$ S( m# @. s; ?
PostgreSQL文档包含一个有用的过程,允许您在数据库内循环此操作。与大多数幼稚的解决方案不同,它可以防止丢失更新和插入竞争。然而,它是READ COMMITTED在模式下工作,只有在事务中执行唯一操作时才是安全的。如果触发器或辅助唯一键导致唯一违规,该功能将无法正常工作。
3 f; `4 a/ Q+ p& _8 ~这个策略效率很低。只要可行,就要把工作排在队列里,按照下面的规定批量加。7 g& ^% z/ A( {) }! z3 p& v
很多尝试解决这个问题的方法都没有考虑回滚,导致更新不完整。两笔交易相互竞争;他们的成功之一INSERTS; 另一个重复的密钥错误,UPDATE而是执行一个。UPDATE等待INSERT回滚或提交块。回滚时,UPDATE重新检查条件会匹配零行,所以即使是UPDATE事实上,提交并没有完成您期望的更新。必要时必须检查结果的行计数并重试。
% w7 k0 l9 r. T. E没有考虑一些尝试解决方案SELECT竞争。如果你尝试简单明了的方法:
: V' N8 X$ j+ ]) N-- 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。一个失败,重复密钥错误。
% k* [) T: e6 W! t/ Z" v) u  L' F1 c这就是为什么你需要重试循环。你可能会认为聪明的使用SQL它可以防止重复的键错误或更新丢失,但你不能这样做。您需要检查行数或处理重复的键错误(取决于所选方法),然后重试。
: H6 p+ b! Q9 x' F' T/ D1 Y( S, \请不要为此使用自己的解决方案。就像消息队列一样,这可能是错误的。& j, M6 H+ q- z
批量更新带锁
$ [  K6 q5 f8 h- ^! _5 Y. B8 ~有时候你想批量上载,这里你有一个新的数据集合并成旧的现有数据集。这远远超过了各行各业upserts只要实用,更高效、更应该是首选。6 E5 P6 t2 ?1 a8 I
在这种情况下,通常按照以下流程进行操作:
' `" n# V. D' E0 h- h3 u1 VCREATE一张TEMPORARY桌子9 L. q" z7 l( S* z6 J- q; W2 D
COPY 或将新数据批量插入临时表" Q6 {8 T& u0 ~
LOCK目标表IN EXCLUSIVE MODE。这允许其他事务对SELECT更改表格,但不能更改。
& z$ A5 P7 l( P% n6 R$ R: G# w: x做一个UPDATE … FROM临时表中使用值的现有记录;  e# d4 j; [$ L* J! ^& _* z
做一个INSERT目标表中已经存在的行;8 Y7 P$ k; ^6 G, p4 Q: y1 ?
COMMIT,释放锁。
2 k" f3 N$ E; ?: X% N6 k9 l例如,对于问题中给出的示例,使用多值INSERT填写临时表:$ t, h! J/ o9 \5 d! b3 Z
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 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;
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则