这里一个非常常见的问题是如何进行upsert,MySQL称之为INSERT。。。在重复更新时,标准支持作为合并操作的一部分
鉴于PostgreSQL不直接支持它(在第9.5页之前),您如何做到这一点?考虑以下事项:
创建表testtable(
id整数主键,
某些数据文本不为空
);
插入到testtable(id,somedata)值中
(1,'弗雷德'),
(2,“鲍勃”);
现在,假设您想要“向上插入”元组(2,'Joe'),(3,'Alan'),那么新的表内容将是:
(1,'fred'),
(2,'Joe'),--现有元组的更改值
(3,'Alan')——添加了新的元组
这就是人们在讨论upsert时所谈论的。至关重要的是,在同一个表上存在多个事务时,任何方法都必须是安全的,无论是通过使用显式锁定,还是以其他方式抵御由此产生的竞争条件
这个主题在Insert上进行了广泛的讨论,关于PostgreSQL?中的重复更新,但这是关于MySQL语法的替代方案,随着时间的推移,它增加了一些不相关的细节。我正在研究最终的答案
这些技术对于“如果不存在则插入,否则什么也不做”也很有用,即“在重复键上插入…忽略”
9.5及更新版本:
PostgreSQL 9.5及更新版本支持INSERT。。。冲突时(键)执行更新(冲突时(键)执行不执行任何操作),即,向上插入
与重复密钥更新时的比较
快速解释
有关用法,请参阅手册-特别是语法图中的冲突\u操作子句和解释性文本
与下面给出的9.4及更旧版本的解决方案不同,此功能可用于多个冲突行,并且不需要独占锁定或重试循环
这里是添加该特性的提交,这里是关于其开发的讨论
如果您使用的是9.5并且不需要向后兼容,您现在可以停止阅读
9.4及以上:
PostgreSQL没有任何内置的UPSERT(或MERGE)功能,并且在并发使用时很难有效地执行
本文详细讨论了这个问题
通常,您必须在两个选项中进行选择:
- 重试循环中的单个插入/更新操作;或
- 锁定表并执行批合并
单行重试循环
如果希望多个连接同时尝试执行插入,那么在重试循环中使用单个行上行是合理的选择
PostgreSQL文档包含一个有用的过程,可以让您在数据库内的循环中执行此操作。与大多数幼稚的解决方案不同,它可以防止丢失更新和插入竞争。它只在读取提交模式下工作,并且只有当它是您在事务中执行的唯一操作时才是安全的。如果触发器或辅助唯一键导致唯一冲突,该函数将无法正常工作
这种策略效率很低。只要可行,您应该排队工作,并按照下面的描述进行批量升级
许多试图解决这个问题的方法没有考虑回滚,因此导致不完整的更新。两项交易相互竞争;其中一个已成功INSERTs;另一个获取重复的密钥错误,并执行更新。UPDATE阻塞等待INSERT回滚或提交。当它回滚时,UPDATE条件重新检查与零行匹配,因此即使UPDATE提交,它实际上也没有完成预期的升级。您必须检查结果行计数,并在必要时重试
一些未遂的解决方案也未能考虑选择种族。如果您尝试简单明了的方法:
——这是错误的。不要复制它。这是一个例子。
开始
更新测试表
设置somedata='blah'
其中id=2;
--记住,这是错误的。不要复制它。
插入到testtable(id,somedata)
选择2,‘废话’
不存在的地方(从testtable中选择1,其中testtable.id=2);
犯罪
然后,当两个系统同时运行时,会出现几种故障模式。一个是已经讨论过的更新重新检查问题。另一种是同时更新,匹配零行并继续。然后,他们都执行EXISTS测试,该测试发生在插入之前。两者都得到零行,因此都执行插入操作。一个失败,出现重复密钥错误
这就是为什么需要一个重试循环。您可能认为,使用聪明的SQL可以防止重复的密钥错误或丢失的更新,但您不能。您需要检查行数或处理重复的键错误(取决于选择的方法),然后重试
请不要为此推出自己的解决方案。与消息队列一样,这可能是错误的
带锁的批量上插
有时,您需要执行大容量upsert,其中有一个新的数据集,您希望将其合并到旧的现有数据集中。这比单独的行加高器的效率要高得多,在可行的情况下应优先考虑
在这种情况下,您通常遵循以下过程:
-
创建一个临时表 -
复制或将新数据批量插入临时表 -
以独占模式锁定目标表。这允许其他事务选择,但不对表进行任何更改 -
执行
更新。。。使用临时表中的值从现有记录的 -
对目标表中不存在的行执行
插入 -
提交,释放锁
例如,对于问题中给出的示例,使用多值INSERT填充临时表:
开始;
创建临时表newvals(id integer,somedata text);
在newvals(id,somedata)中插入值(2,'Joe'),(3,'Alan');
在独占模式下锁定表testtable;
更新测试表
SET somedata=newvals.somedata
来自纽瓦尔斯
其中newvals.id=testtable.id;
插入到测试表中
选择newvals.id、newvals.somedata
来自纽瓦尔斯
左侧外部联接testtable打开(testtable.id=newvals.id)
其中testtable.id为NULL;
犯罪
相关阅读
- 插入wiki页面
- 研究生的高出生率
- 在PostgreSQL中重复更新时插入
- http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
- 向上插入事务
- 函数中的SELECT或INSERT是否容易出现竞争条件
- PostgreSQL wiki上的SQL
MERGE - 目前在Postgresql中实现UPSERT最常用的方法
合并怎么样
SQL标准MERGE实际上定义不好的并发语义,不适合在不先锁定表的情况下进行升级
对于数据合并来说,它是一个非常有用的OLAP语句,但对于并发安全的upsert来说,它实际上并不是一个有用的解决方案。对于使用其他DBMS的人,有很多建议使用MERGE进行升级,但实际上这是错误的
其他数据库:
插入。。。MySQL中的重复密钥更新- 来自MS SQL Server的
MERGE(但请参见上文关于MERGE问题) - 来自Oracle的
MERGE(但请参见上文关于MERGE问题的内容)