在更新插入中返回旧行值

问题描述 投票:0回答:1

我有下表:

mytable
-------
id
top
top_timestamp

我想更新插入

top
top_timestamp
,同时返回旧值(如果有)。如果该行已经存在,只有当我尝试插入的
top
的值>当前值

时,我才想更新它

该解决方案还必须考虑并发写入。

对于更新插入,到目前为止我有这个:

INSERT INTO mytable
AS mt (id, top, top_timestamp)
VALUES ('some-id', 123, 999)
ON CONFLICT (id)
DO UPDATE SET top=123, top_timestamp=999
WHERE mt.top < 123

我意识到

RETURNING
只能引用新值,因此要返回旧值,我有这个:

WITH old_values AS (SELECT top, top_timestamp FROM mytable WHERE id = 'some-id' FOR UPDATE), 
update AS ($upsertSqlAbove)
SELECT top AS old_top, top_timestamp AS old_top_timestamp FROM old_values

这似乎可行,但是并发写入安全吗?

SELECT FOR UPDATE
是否按我的预期工作?也就是说,它会锁定该行直到整个查询完成?

第一个查询和这个有什么区别:

INSERT INTO mytable
AS mt (id, top, top_timestamp)
VALUES ('some-id', 123, 999)
ON CONFLICT (id)
DO UPDATE SET top=123, top_timestamp=999
WHERE mt.top < 123
RETURNING (SELECT top FROM mytable WHERE id = 123) AS last_top, 
          (SELECT top_timestamp FROM mytable WHERE id = 123) AS last_top_timestamp
sql postgresql concurrency common-table-expression
1个回答
0
投票

第一个查询和这个有什么区别:

要么报告刚刚以并发安全方式更新的最后一行版本的旧值。但第一个更贵。它会提前锁定现有行。因此它持有锁的时间更长,并且可能会更长时间地阻止对同一行的并发写入。它还执行另外一条语句。而且,最重要的是,即使稍后没有发生

UPDATE
,它也会锁定该行,在这种情况下这完全是浪费。

所以第二个查询更好。除了两个缺陷:

  • 它为每一列运行单独的

    SELECT
    。这似乎是必要的,因为
    RETURNING
    子句中的子查询表达式在这方面受到限制。

  • 即使在

    INSERT
    之后,它也会运行这些 SELECT 查询。那么
    SELECT
    肯定是空的,但仍然是全额成本。

第三个查询修复了这两个缺陷:

INSERT INTO tbl AS t (id, top, top_timestamp)
VALUES ('some-id', 123, 999)
ON     CONFLICT (id) DO UPDATE
SET    top = EXCLUDED.top                      -- !
     , top_timestamp = EXCLUDED.top_timestamp  -- !
WHERE  t.top < EXCLUDED.top                    -- !
RETURNING (SELECT t_old FROM tbl t_old
           WHERE  t_old.id = t.id
           AND    t.xmax <> 0                  -- actually was an UPDATE!
          ).*

另外,使用特殊的 ROW 变量

EXCLUDED
。它保存建议插入的行,并有助于避免重复拼写输入值。 (细微差别:所有默认值和触发器都已应用。)

© www.soinside.com 2019 - 2024. All rights reserved.