|
这里一个非常常见的问题是如何进行upsert,这是MySQL调用内容,INSERT ... ON DUPLICATE UPDATE该标准支持该标准MERGE操作。
4 F' M$ l! K2 k! d4 ^ U" L8 [鉴于PostgreSQL9..5页前),你是怎么做到的?考虑以下几点:
* z* A6 h: g( K' v9 uCREATE TABLE testtable ( id integer PRIMARY KEY, somedata text NOT NULL);INSERT INTO testtable (id,somedata) VALUES(1,'fred(二),bob');假设你现在想UPSERT元组(2,‘Joe’),(3,‘Alan’),因此,新表的内容是:
# e( |7 T9 h |, B2 E# a) d; I0 @- I(1,'fred(二),Joe -- Changed value of existing tuple(3,'Alan -- Added new tuple也就是说,人们在讨论论的话题upsert。至关重要的是,在多个事务处理相同表格时,任何方法都必须安全-通过使用显式锁定或以其他方式抵此产生的竞争条件。
) b b4 h: U+ S. I1 J/ ~关于插入PostgreSQL重复更新,对主题进行了广泛的讨论。,但这是关于MySQL随着时间的推移,语法的替代方法增加了许多无关的细节。我正在努力确定答案。
2 T9 `" t" E. E2 f3 c S3 C这些技术也可用于如果没有,则插入,否则不执行任何操作…”。
% T# W/ Q& O6 C( \% t7 I * d( g" q. z2 q# }, |/ e
解决方案:
0 g+ p1 d2 B+ s: u 9.5及更高版本:PostgreSQL 9.支持更高版本INSERT ... ON CONFLICT (key) DO UPDATE(和ON CONFLICT (key) DO NOTHING),即upsert。
$ j3 g5 y) w* r( E3 J( G与的比较ON DUPLICATE KEY UPDATE。, @- L6 X! r& L2 D$ J
快速解释。. b# J, u+ N+ K7 V9 u. s' Y5 U
请参阅手册,特别是语法图中的相关用法conflict_action以及解释性文字。6 V; X# s8 }% j9 K' \5 q! X: u" _/ U
以下9.4.早期版本的解决方案不同。该功能可用于多个冲突行,无需排他锁定或重试循环。6 k3 `$ c2 l7 _
这里提交添加功能,这里讨论功能开发。
}6 G* O. `. G( p假如你用9.5.如果不需要向后兼容,可以立即停止阅读。6 e( {3 H% D' D7 v( |; {2 N/ I9 j/ `
9.4及更高版本:; f% \0 J# u7 @1 E
PostgreSQL没有内置UPSERT(或MERGE)很难有效地面对并发使用。
3 Z9 n. W! m& t3 s5 x本文详细讨论了这个问题。
9 p }* _* G. I1 d) W: W通常,您必须在两个选项之间进行选择: y4 c! z0 Q" [( C! v
重试循环中的每个插入/更新操作;或
- V% ]7 W- o5 y4 ?# |. P$ E锁定表并分批合并" q8 i7 [1 g1 H
个别行重试循环
3 h$ _ k: k; S( m* P' [/ J# G如果您想同时尝试插入多个连接,则在重试循环中使用单个行高插入是合理的选择。- n7 t1 Z0 t. G% J
PostgreSQL文档包含一个有用的过程,允许您在数据库内循环此操作。与大多数幼稚的解决方案不同,它可以防止丢失更新和插入竞争。然而,它是READ COMMITTED在模式下工作,只有在事务中执行唯一操作时才是安全的。如果触发器或辅助唯一键导致唯一违规,该功能将无法正常工作。
: o5 z1 f& s, e4 t. i3 C4 y这个策略效率很低。只要可行,就要把工作排在队列里,按照下面的规定批量加。( \' c: ?6 I4 O4 {4 ~% V
很多尝试解决这个问题的方法都没有考虑回滚,导致更新不完整。两笔交易相互竞争;他们的成功之一INSERTS; 另一个重复的密钥错误,UPDATE而是执行一个。UPDATE等待INSERT回滚或提交块。回滚时,UPDATE重新检查条件会匹配零行,所以即使是UPDATE事实上,提交并没有完成您期望的更新。必要时必须检查结果的行计数并重试。
, a/ M) O9 q/ ^, G0 ~: P& ~( i没有考虑一些尝试解决方案SELECT竞争。如果你尝试简单明了的方法:9 a" `) }8 u8 V) Z
-- 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。一个失败,重复密钥错误。
! N8 ?: p3 m: s+ ]0 R6 L6 T, B这就是为什么你需要重试循环。你可能会认为聪明的使用SQL它可以防止重复的键错误或更新丢失,但你不能这样做。您需要检查行数或处理重复的键错误(取决于所选方法),然后重试。
" r/ V D. \; v9 U) D; C2 Q请不要为此使用自己的解决方案。就像消息队列一样,这可能是错误的。- e* [5 J8 x, \8 F1 q# Y
批量更新带锁
% }0 ?# d8 }6 V: }有时候你想批量上载,这里你有一个新的数据集合并成旧的现有数据集。这远远超过了各行各业upserts只要实用,更高效、更应该是首选。
4 N/ Q9 r/ T+ q9 z* \/ o, ^在这种情况下,通常按照以下流程进行操作:, v0 A, f9 i8 g7 q
CREATE一张TEMPORARY桌子" |9 p' n" l: C
COPY 或将新数据批量插入临时表 T, Q C- M, Y [; Y" r; d
LOCK目标表IN EXCLUSIVE MODE。这允许其他事务对SELECT更改表格,但不能更改。
9 M: o& v" d [. t0 q( C$ s做一个UPDATE … FROM临时表中使用值的现有记录;6 Z$ j! F7 c/ `! x
做一个INSERT目标表中已经存在的行;$ z$ V8 U7 m$ C
COMMIT,释放锁。
2 y) ?# j9 W+ ]) D1 X例如,对于问题中给出的示例,使用多值INSERT填写临时表:
& N* f b6 Q; [9 ^# f" xBEGIN;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; |
|