update/insert 操作的最佳温度 table 策略
best temp table strategy for update/insert operation
我从远程服务器得到了一份交易记录列表,其中一些已经存在于我们的数据库中,一些是新的。我的任务是更新已经存在的并插入不存在的。假设事务具有不依赖于我的本地数据库的远程 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;
换句话说:
- 创建一个仅在事务生命周期内存在的临时文件 table。
- 将我所有的记录转储到临时 table。
- 在一条语句中完成所有更新,同时从临时文件中删除更新的记录 table。
- 将临时 table 中剩余的所有内容插入永久 table,因为它不是更新。
但后来我开始怀疑使用每会话临时 table 并且不将所有操作包装在单个事务中是否更有效。我的数据库会话只会被单个线程使用,所以这应该是可能的:
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;
我的想法:
这避免了每次收到新的一批记录时都必须创建临时文件 table。相反,如果已经使用此数据库会话处理了一个批次(这很可能),则 table 将已经存在。
这节省了回滚 space 因为我没有在单个事务中将多个操作串在一起。不要求整个 update/insert 操作是原子的;我使用事务的唯一原因是临时 table 会在提交时自动删除。
后一种方法可能优于前一种方法吗?这两种方法有什么特殊的 "gotchas" 我应该注意的吗?
您所描述的通常称为 upsert。甚至官方文档也提到了它,在这里:http://www.postgresql.org/docs/current/static/plpgsql-control-structures.html#PLPGSQL-UPSERT-EXAMPLE
更新插入的最大问题是并发问题,如下所述:http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/ and here: http://johtopg.blogspot.com.br/2014/04/upsertisms-in-postgres.html
我认为你的方法很好,虽然我根本不会使用临时 table,而是将 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+ 将支持开箱即用的并发更新插入,使用 INSERT ... ON CONFLICT DO NOTHING/UPDATE 语法。
我从远程服务器得到了一份交易记录列表,其中一些已经存在于我们的数据库中,一些是新的。我的任务是更新已经存在的并插入不存在的。假设事务具有不依赖于我的本地数据库的远程 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;
换句话说:
- 创建一个仅在事务生命周期内存在的临时文件 table。
- 将我所有的记录转储到临时 table。
- 在一条语句中完成所有更新,同时从临时文件中删除更新的记录 table。
- 将临时 table 中剩余的所有内容插入永久 table,因为它不是更新。
但后来我开始怀疑使用每会话临时 table 并且不将所有操作包装在单个事务中是否更有效。我的数据库会话只会被单个线程使用,所以这应该是可能的:
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;
我的想法:
这避免了每次收到新的一批记录时都必须创建临时文件 table。相反,如果已经使用此数据库会话处理了一个批次(这很可能),则 table 将已经存在。
这节省了回滚 space 因为我没有在单个事务中将多个操作串在一起。不要求整个 update/insert 操作是原子的;我使用事务的唯一原因是临时 table 会在提交时自动删除。
后一种方法可能优于前一种方法吗?这两种方法有什么特殊的 "gotchas" 我应该注意的吗?
您所描述的通常称为 upsert。甚至官方文档也提到了它,在这里:http://www.postgresql.org/docs/current/static/plpgsql-control-structures.html#PLPGSQL-UPSERT-EXAMPLE
更新插入的最大问题是并发问题,如下所述:http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/ and here: http://johtopg.blogspot.com.br/2014/04/upsertisms-in-postgres.html
我认为你的方法很好,虽然我根本不会使用临时 table,而是将 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+ 将支持开箱即用的并发更新插入,使用 INSERT ... ON CONFLICT DO NOTHING/UPDATE 语法。