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_idPRIMARY KEYFOREIGN 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 保证原子的INSERTUPDATE 结果; 如果没有独立误差,这两个结果之一是 保证,即使在高并发下。这也称为UPSERT – “UPDATEINSERT”。

这是您要达到的目标的最佳实践。

【讨论】:

我用INSERT ... ON CONFLICT DO UPDATE实现,如果有主键自动递增SERIALSERIAL每次都会递增,无论是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 中的条件 INSERT INTO 语句

Postgres UPSERT (INSERT 或 UPDATE) 仅当值不同时

使用TRANSACTION postgres进行双重INSERT

Postgres INSERT 为 json 返回“无效的输入语法”