回答

收藏

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

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

这里一个非常常见的问题是如何进行upsert,这是MySQL调用内容,INSERT ... ON DUPLICATE UPDATE该标准支持该标准MERGE操作。7 _8 x. d/ H3 f
鉴于PostgreSQL9..5页前),你是怎么做到的?考虑以下几点:
6 d" `+ h! e* B# DCREATE TABLE testtable (    id integer PRIMARY KEY,   somedata text NOT NULL);INSERT INTO testtable (id,somedata) VALUES(1,'fred(二),bob');假设你现在想UPSERT元组(2,‘Joe’),(3,‘Alan’),因此,新表的内容是:
: B, x; U1 `8 I9 \: X3 l! k" c: t% J, ^(1,'fred(二),Joe  -- Changed value of existing tuple(3,'Alan    -- Added new tuple也就是说,人们在讨论论的话题upsert。至关重要的是,在多个事务处理相同表格时,任何方法都必须安全-通过使用显式锁定或以其他方式抵此产生的竞争条件。! a; L& R* U& z# x- ^! Q
关于插入PostgreSQL重复更新,对主题进行了广泛的讨论。,但这是关于MySQL随着时间的推移,语法的替代方法增加了许多无关的细节。我正在努力确定答案。
/ U1 c2 e+ V( D' C4 h0 c: F这些技术也可用于如果没有,则插入,否则不执行任何操作…”。
- u' U# R0 }& v( Z                                                               
) N. I; V* F$ {7 u: |    解决方案:                                                                : J" b" E4 S0 `" M6 W
                                                                9.5及更高版本:PostgreSQL 9.支持更高版本INSERT ... ON CONFLICT (key) DO UPDATE(和ON CONFLICT (key) DO NOTHING),即upsert。
3 U, k5 _- M+ s7 t与的比较ON DUPLICATE KEY UPDATE。1 v  V- z  |) F- U5 M1 f; m2 z+ X
快速解释。
1 V  P6 b7 ^! C  P+ y请参阅手册,特别是语法图中的相关用法conflict_action以及解释性文字。, j4 b, p) U" P9 m
以下9.4.早期版本的解决方案不同。该功能可用于多个冲突行,无需排他锁定或重试循环。. V8 B& I* |2 e
这里提交添加功能,这里讨论功能开发。
' J0 ]# T3 e' J0 p/ H" k+ X假如你用9.5.如果不需要向后兼容,可以立即停止阅读。
3 T! y2 ?' X5 [1 f9.4及更高版本:
0 {4 R$ I( U1 c7 a3 BPostgreSQL没有内置UPSERT(或MERGE)很难有效地面对并发使用。
8 y3 X) F' d8 n: [7 G2 H' i4 n本文详细讨论了这个问题。) g7 K5 s. _9 K% M% d0 Y
通常,您必须在两个选项之间进行选择:
* q( p! k, j( X+ g$ x& A1 E- s重试循环中的每个插入/更新操作;或
/ C. j8 s6 ?, Q8 v$ {6 ]$ V* [) S5 w( s锁定表并分批合并
$ e/ A/ E8 j% D个别行重试循环
5 Q& q1 I- @3 N( X+ p( s如果您想同时尝试插入多个连接,则在重试循环中使用单个行高插入是合理的选择。& ]/ m: S; |% `' R# W7 ~
PostgreSQL文档包含一个有用的过程,允许您在数据库内循环此操作。与大多数幼稚的解决方案不同,它可以防止丢失更新和插入竞争。然而,它是READ COMMITTED在模式下工作,只有在事务中执行唯一操作时才是安全的。如果触发器或辅助唯一键导致唯一违规,该功能将无法正常工作。$ {7 G4 o: e3 b6 N5 K# R- `- k' G
这个策略效率很低。只要可行,就要把工作排在队列里,按照下面的规定批量加。
$ S2 R) u# e+ g6 \4 M; ?  r很多尝试解决这个问题的方法都没有考虑回滚,导致更新不完整。两笔交易相互竞争;他们的成功之一INSERTS; 另一个重复的密钥错误,UPDATE而是执行一个。UPDATE等待INSERT回滚或提交块。回滚时,UPDATE重新检查条件会匹配零行,所以即使是UPDATE事实上,提交并没有完成您期望的更新。必要时必须检查结果的行计数并重试。4 B% r& j% x5 k2 y5 ?" }! O8 s
没有考虑一些尝试解决方案SELECT竞争。如果你尝试简单明了的方法:
& y% M! _2 w) B0 F5 g+ F' I-- 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。一个失败,重复密钥错误。( t  S5 A7 H9 n2 A. [# T, l
这就是为什么你需要重试循环。你可能会认为聪明的使用SQL它可以防止重复的键错误或更新丢失,但你不能这样做。您需要检查行数或处理重复的键错误(取决于所选方法),然后重试。
4 M* \, m  c# `% P! d- F/ e. k请不要为此使用自己的解决方案。就像消息队列一样,这可能是错误的。* `* V; \/ R" a# o4 F% ?
批量更新带锁
$ l/ E. ?3 y  ?0 m. g5 Q: i有时候你想批量上载,这里你有一个新的数据集合并成旧的现有数据集。这远远超过了各行各业upserts只要实用,更高效、更应该是首选。' i: r$ R, {$ B: O
在这种情况下,通常按照以下流程进行操作:
; W  F# F2 ?& }; X1 P* eCREATE一张TEMPORARY桌子" ^0 b+ q0 K) o+ Z
COPY 或将新数据批量插入临时表+ f7 i/ j2 ?3 o" H6 y6 [" b
LOCK目标表IN EXCLUSIVE MODE。这允许其他事务对SELECT更改表格,但不能更改。
. g% D0 S6 c! I$ H做一个UPDATE … FROM临时表中使用值的现有记录;( N: e5 Y# b% j& z3 X/ J
做一个INSERT目标表中已经存在的行;% V" o* l, m, }9 G3 c
COMMIT,释放锁。
3 s! C  g8 K# @! Z) E' m% ]例如,对于问题中给出的示例,使用多值INSERT填写临时表:
9 P! p; U" `1 d, aBEGIN;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;
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则