我给出了一个来自远程服务器的事务记录列表,其中一些已存在于我们的数据库中,其中一些是新的。我的任务是更新已经存在的那些并插入那些不存在的那些。假设事务具有不依赖于我的本地数据库的远程ID。列表的大小可以是1到500之间的任何值。
数据库是postgresql。
我最初的想法是这样的:
BEGIN
CREATE TEMP TABLE temp_transactions (LIKE transactions) ON COMMIT DROP;
INSERT INTO temp_transactions(...) VALUES (...);
WITH updated_transactions AS (...update statement...)
DELETE FROM temp_transactions USING updated_transactions
WHERE temp_transactions.external_id = updated_transactions.external_id;
INSERT INTO transactions SELECT ... FROM temp_transactions;
COMMIT;
换句话说:
但后来我开始怀疑使用每会话临时表是否更有效,而不是将所有操作包装在单个事务中。我的数据库会话只会由单个线程使用,所以这应该是可能的:
CREATE TEMP TABLE temp_transactions IF NOT EXISTS (LIKE transactions);
INSERT INTO temp_transactions(...) VALUES (...);
WITH updated_transactions AS (...update statement...)
DELETE FROM temp_transactions USING updated_transactions
WHERE temp_transactions.external_id = updated_transactions.external_id;
INSERT INTO transactions SELECT ... FROM temp_transactions;
TRUNCATE temp_transactions;
我的想法:
这样可以避免每次收到新批记录时都必须创建临时表。相反,如果已经使用此数据库会话(很可能)处理了批次,则表格已经存在。
这节省了回滚空间,因为我没有在单个事务中将多个操作串联在一起。并不要求整个更新/插入操作是原子的;我使用事务的唯一原因是临时表会在提交时自动删除。
后一种方法可能优于前者吗?两种方法都有任何特殊的"陷阱"我应该知道吗?
答案 0 :(得分:1)
您所描述的通常称为upsert。甚至官方文档也提到了这里:http://www.postgresql.org/docs/current/static/plpgsql-control-structures.html#PLPGSQL-UPSERT-EXAMPLE
upserts的最大问题是并发性问题,如下所述:http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/和此处:http://johtopg.blogspot.com.br/2014/04/upsertisms-in-postgres.html
我认为你的方法很好,虽然我根本不会使用临时表,并将VALUES部分放入UPDATE部分,使整个事情成为一个单一的声明。
像这样:
CREATE TABLE test (id int, data int);
CREATE TABLE
WITH new_data (id, data) AS (
VALUES (1, 2), (2, 6), (3, 10)
),
updated AS (
UPDATE test t
SET data = v.data
FROM new_data v
WHERE v.id = t.id
RETURNING t.id
)
INSERT INTO test
SELECT *
FROM new_data v
WHERE NOT EXISTS (
SELECT 1
FROM updated u
WHERE u.id = v.id
);
INSERT 0 3
SELECT * FROM test;
id | data
----+------
1 | 2
2 | 6
3 | 10
(3 rows)
WITH new_data (id, data) AS (
VALUES (1, 20), (2, 60), (4, 111)
),
updated AS (
UPDATE test t
SET data = v.data
FROM new_data v
WHERE v.id = t.id
RETURNING t.id
)
INSERT INTO test
SELECT *
FROM new_data v
WHERE NOT EXISTS (
SELECT 1
FROM updated u
WHERE u.id = v.id
);
INSERT 0 1
SELECT * FROM test;
id | data
----+------
3 | 10
1 | 20
2 | 60
4 | 111
(4 rows)
PG 9.5+将支持开箱即用的并发upsert,使用INSERT ... ON CONFLICT DOHING / UPDATE语法。