Postgres INSERT ON CONFLICT DO UPDATE 与 INSERT 或 UPDATE
Posted
技术标签:
【中文标题】Postgres INSERT ON CONFLICT DO UPDATE 与 INSERT 或 UPDATE【英文标题】:Postgres INSERT ON CONFLICT DO UPDATE vs INSERT or UPDATE 【发布时间】:2018-08-02 00:59:25 【问题描述】:我有stock_price_alert
3 列的表。 stock_price_id
是 PRIMARY KEY
和 FOREIGN KEY
到其他表。表定义如下:
create table stock_price_alert (
stock_price_id integer references stock_price (id) on delete cascade not null,
fall_below_alert boolean not null,
rise_above_alert boolean not null,
primary key (stock_price_id)
);
我需要:
1) INSERT
如果不存在则记录
-- query 1
INSERT INTO stock_price_alert (stock_price_id, fall_below_alert, rise_above_alert)
VALUES (1, true, false);
2) UPDATE
记录(如果存在)
-- query 2
UPDATE stock_price_alert SET
fall_below_alert = true,
rise_above_alert = false
WHERE stock_price_id = 1;
首先我需要对stock_price_alert
表发出SELECT
查询,以确定是执行查询(1)还是(2)。
Postgres 支持INSERT INTO TABLE .... ON CONFLICT DO UPDATE ...
:
-- query 3
INSERT INTO stock_price_alert (stock_price_id, fall_below_alert, rise_above_alert)
VALUES (1, true, false)
ON CONFLICT (stock_price_id) DO UPDATE SET
fall_below_alert = EXCLUDED.fall_below_alert,
rise_above_alert = EXCLUDED.rise_above_alert;
我可以始终使用查询 (3),而不是使用查询 (1) 或 (2)?然后我不需要事先发出SELECT
查询,它有助于简化代码。
但我想知道,最佳做法是什么?查询 (3) 会导致性能问题或不必要的副作用吗?谢谢。
【问题讨论】:
【参考方案1】:查询 3 是 Postgres 9.5 中引入的“UPSERT”(= UPDATE 或 INSERT)的 Postgres 语法。
来自documentation:
ON CONFLICT DO UPDATE
保证原子的INSERT
或UPDATE
结果; 如果没有独立误差,这两个结果之一是 保证,即使在高并发下。这也称为UPSERT
– “UPDATE
或INSERT
”。
这是您要达到的目标的最佳实践。
【讨论】:
我用INSERT ... ON CONFLICT DO UPDATE
实现,如果有主键自动递增SERIAL
,SERIAL
每次都会递增,无论是INSERT还是UPDATE。这在 SERIAL 中造成了差距,尽管不是什么大问题。但是对于这张表来说,主键不是SERIAL
,所以这里就没有这个缺点了。我决定在这里只使用query 3
。感谢您的帮助,不胜感激!
@shuwn-yuan-tee 在自动增量中存在间隙没有缺点。即使对于不基于堆的布局的 DBMS 来说也没什么大不了的。如果您每天插入 + 删除百万行,而不使用 bigserial,则唯一可能出现的串行间隙问题。【参考方案2】:
我注意到/测试过,除了 ON CONFLICT 之外,INSERTS(尚未测试 UPSERTS)使用 WHERE NOT EXISTS 的速度要快得多。通常比仅允许 ON CONFLICT 处理存在检查快约 3 倍。我认为这可能会延续到 UPSERTS 中,从而可能更快地执行 INSERT 然后更新。这是我对插入的测试...
--so i can keep rerunning
DROP TABLE if exists temp1;
DROP TABLE if exists temp2;
--create a billion rows
SELECT GENERATE_SERIES AS id INTO TEMP temp1
FROM GENERATE_SERIES(1, 10000000);
CREATE UNIQUE INDEX ux_id ON temp1(id);
ALTER TABLE temp1 CLUSTER ON ux_id;
--create a second table to insert from, with the same data
SELECT * INTO TEMP temp2
FROM temp1;
CREATE UNIQUE INDEX ux_id2 ON temp2(id);
ALTER TABLE temp2 CLUSTER ON ux_id2;
--test inserting with on conflict only
INSERT INTO temp1(id)
SELECT id
FROM temp2 ON conflict DO nothing;
--execution time: 14.71s (1million rows)
--test inserting with not exists and on conflict
INSERT INTO temp1(id)
SELECT t2.id
FROM temp2 t2
WHERE NOT EXISTS (SELECT 1 FROM temp1 t1 WHERE t2.id = t1.id)
ON conflict DO nothing;
--execution time: 5.78s (1million rows)
【讨论】:
我只使用了几个星期的 postgresql,所以对我的发现持保留态度... 在您的示例中,您不执行 UPSERT。WHERE NOT EXISTS
自动消除 ON conflict DO nothing
。您的示例之间的区别在于,在第一种情况下,您通过 ON conflict DO nothing
捕获异常,而在第二种情况下,您不允许它们出现。这显然更便宜,更快以上是关于Postgres INSERT ON CONFLICT DO UPDATE 与 INSERT 或 UPDATE的主要内容,如果未能解决你的问题,请参考以下文章
Sequelize Postgres - 如何使用 ON CONFLICT 来实现独特的?
Postgres INSERT 导致 UnhandledPromiseRejectionWarning
Postgres UPSERT (INSERT 或 UPDATE) 仅当值不同时