Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/postgresql/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在PostgreSQL中升级(合并、插入…重复更新)?_Postgresql_Insert Update_Upsert_Sql Merge - Fatal编程技术网

如何在PostgreSQL中升级(合并、插入…重复更新)?

如何在PostgreSQL中升级(合并、插入…重复更新)?,postgresql,insert-update,upsert,sql-merge,Postgresql,Insert Update,Upsert,Sql Merge,这里一个非常常见的问题是如何进行upsert,MySQL称之为INSERT。。。在重复更新时,标准支持作为合并操作的一部分 鉴于PostgreSQL不直接支持它(在第9.5页之前),您如何做到这一点?考虑以下事项: CREATE TABLE testtable ( id integer PRIMARY KEY, somedata text NOT NULL ); INSERT INTO testtable (id, somedata) VALUES (1, 'fred'), (

这里一个非常常见的问题是如何进行upsert,MySQL称之为
INSERT。。。在重复更新时
,标准支持作为合并操作的一部分

鉴于PostgreSQL不直接支持它(在第9.5页之前),您如何做到这一点?考虑以下事项:

CREATE TABLE testtable (
    id integer PRIMARY KEY,
    somedata text NOT NULL
);

INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');
现在,假设您想要“向上插入”元组
(2,'Joe')
(3,'Alan')
,那么新的表内容将是:

(1, 'fred'),
(2, 'Joe'),    -- Changed value of existing tuple
(3, 'Alan')    -- Added new tuple
这就是人们在讨论
upsert
时所谈论的。至关重要的是,在同一个表上存在多个事务时,任何方法都必须是安全的——要么使用显式锁定,要么以其他方式抵御由此产生的竞争条件

本主题在上进行了广泛讨论,但这是关于MySQL语法的替代方案,随着时间的推移,它增加了一些不相关的细节。我正在研究最终的答案

这些技术对于“如果不存在则插入,否则不执行任何操作”也很有用,即“在重复键上插入…忽略”。

9.5及更新版本: PostgreSQL 9.5及更新版本支持
INSERT。。。冲突时(键)执行更新(冲突时(键)执行不执行任何操作),即,向上插入

有关用法,请参见语法图中的conflict_action子句,以及

与下面给出的9.4及更旧版本的解决方案不同,此功能可用于多个冲突行,并且不需要独占锁定或重试循环


如果您使用的是9.5版本,并且不需要向后兼容,那么现在可以停止阅读


9.4及以上: PostgreSQL没有任何内置的
UPSERT
(或
MERGE
)功能,并且在并发使用时很难有效地执行

通常,您必须在两个选项中进行选择:

  • 重试循环中的单个插入/更新操作;或
  • 锁定表并执行批合并
单行重试循环 如果希望多个连接同时尝试执行插入,那么在重试循环中使用单个行上行是合理的选择

。与大多数幼稚的解决方案不同,它可以防止丢失更新和插入竞争。它只在
读取提交
模式下工作,并且只有当它是您在事务中执行的唯一操作时才是安全的。如果触发器或辅助唯一键导致唯一冲突,该函数将无法正常工作

这种策略效率很低。只要可行,您应该排队工作,并按照下面的描述进行批量升级

许多试图解决这个问题的方法没有考虑回滚,因此导致不完整的更新。两项交易相互竞争;其中一个已成功
INSERT
s;另一个获取重复的密钥错误,并执行
更新
UPDATE
阻塞等待
INSERT
回滚或提交。当它回滚时,
UPDATE
条件重新检查与零行匹配,因此即使
UPDATE
提交,它实际上也没有完成预期的升级。您必须检查结果行计数,并在必要时重试

一些未遂的解决方案也未能考虑选择种族。如果您尝试简单明了的方法:

-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.

BEGIN;

UPDATE testtable
SET 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;
然后,当两个系统同时运行时,会出现几种故障模式。一个是已经讨论过的更新重新检查问题。另一种是同时更新
,匹配零行并继续。然后,它们都执行
EXISTS
测试,该测试发生在
插入之前。两者都得到零行,因此都执行
插入操作。一个失败,出现重复密钥错误

这就是为什么需要一个重试循环。您可能认为,使用聪明的SQL可以防止重复的密钥错误或丢失的更新,但您不能。您需要检查行数或处理重复的键错误(取决于选择的方法),然后重试

请不要为此推出自己的解决方案。与消息队列一样,这可能是错误的

带锁的批量上插 有时,您需要执行大容量upsert,其中有一个新的数据集,您希望将其合并到旧的现有数据集中。这比单独的上排要有效得多,在可行的情况下应优先考虑

在这种情况下,您通常遵循以下过程:

  • 创建一个
    临时表

  • 复制
    或将新数据批量插入临时表

  • 以独占模式锁定目标表
    。这允许其他事务
    选择
    ,但不对表进行任何更改

  • 执行
    更新。。。使用临时表中的值从现有记录的

  • 对目标表中不存在的行执行
    插入

  • 提交
    ,释放锁

例如,对于问题中给出的示例,使用多值
INSERT
填充临时表:

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 testtable
SET somedata = newvals.somedata
FROM newvals
WHERE newvals.id = testtable.id;

INSERT INTO testtable
SELECT newvals.id, newvals.somedata
FROM newvals
LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
WHERE testtable.id IS NULL;

COMMIT;
相关阅读
合并怎么样? SQL标准
MERGE
实际上定义不好的并发语义,不适合在不先锁定表的情况下进行升级

对于数据合并来说,它是一个非常有用的OLAP语句,但对于并发安全的upsert来说,它实际上并不是一个有用的解决方案。对于使用其他DBMS的人,有很多建议使用
MERGE
进行升级,但实际上这是错误的

其他数据库:
  • (但请参见上文关于
    MERGE
    问题的内容)
  • (但请参见上文关于
    MERGE
    问题的内容)

我正试图用另一种解决方案来帮助
do $$
begin 
  insert into testtable(id, somedata) values(2,'Joe');
exception when unique_violation then
  update testtable set somedata = 'Joe' where id = 2;
end $$;
WITH UPD AS (UPDATE TEST_TABLE SET SOME_DATA = 'Joe' WHERE ID = 2 
RETURNING ID),
INS AS (SELECT '2', 'Joe' WHERE NOT EXISTS (SELECT * FROM UPD))
INSERT INTO TEST_TABLE(ID, SOME_DATA) SELECT * FROM INS
from sqlalchemy.dialects.postgresql import insert

stmt = insert(my_table).values(user_email='a@b.com', data='inserted data')
stmt = stmt.on_conflict_do_update(
    index_elements=[my_table.c.user_email],
    index_where=my_table.c.user_email.like('%@gmail.com'),
    set_=dict(data=stmt.excluded.data)
    )
conn.execute(stmt)
insert into dummy(id, name, size) values(1, 'new_name', 3)
on conflict do nothing;`  
insert into dummy(id, name, size) values(1, 'new_name', 3)
on conflict(id)
do update set name = 'new_name', size = 3;  
insert into dummy(id, name, size) values(1, 'new_name', 3)
on conflict on constraint dummy_pkey
do update set name = 'new_name', size = 4;