代码之家  ›  专栏  ›  技术社区  ›  Burg

冲突时如何返回Postgres插入中列的旧值do update?

  •  5
  • Burg  · 技术社区  · 6 年前

    我试图在Postgres中运行一个“upsert”,比如:

    INSERT INTO my_table (
        id, -- unique key
        name,
        hash
    ) VALUES (
        '4b544dea-b355-463c-8fba-40c36ac7cb0c',
        'example',
        '0x0123456789'
    ) ON CONFLICT (
        id
    ) DO UPDATE SET
        name = 'example',
        hash = '0x0123456789'
    RETURNING
        OLD.hash;
    

    我想返回 hash 列(上面的示例不是有效的查询,因为 OLD 不是有效的表别名)。重要的是,我正试图找到一种方法,这种方法不会在负载下引起冲突。这是可能的吗?或者是在事务中执行写前读取的唯一解决方案?

    3 回复  |  直到 6 年前
        1
  •  1
  •   Laurenz Albe    6 年前

    这个 RETURNING 条款 INSERT 甚至不会返回 UPDATE 分支。

    使用 SELECT ... FOR UPDATE RETURNING ... 似乎是您的最佳选择,但请注意,存在一个较小的竞争条件-可以在 SELECT 以及随后的 插入 这将使后者失败。

    使用 REPEATABLE READ 事务处理和在出现问题时重试操作可能会有所帮助。

        2
  •  1
  •   Burg    6 年前

    我最终找到了一个解决方法,尽管它不能严格保证原子性,但一般来说,根据您的用例,这可能是一个很好的策略。

    INSERT INTO my_table (
        id, -- unique key
        name,
        hash
    ) VALUES (
        '4b544dea-b355-463c-8fba-40c36ac7cb0c',
        'example',
        '0x0123456789'
    ) ON CONFLICT (
        id
    ) DO UPDATE SET
        name = 'example',
        hash = '0x0123456789'
    RETURNING
        name,
        hash, -- will be the new hash
        (SELECT hash FROM my_table WHERE my_table.id = '4b544dea-b355-463c-8fba-40c36ac7cb0c') -- will be the old hash
        ;
    
        3
  •  1
  •   Scott B    6 年前

    另一个可能的答案是,如果您愿意更新模式,将前一个值存储在另一列中:

    ALTER table my_table add column prev_hash text;
    
    INSERT INTO my_table (
        id, -- unique key
        hash
    ) VALUES (
        1,
        'a'
    ) ON CONFLICT (
        id
    ) DO UPDATE SET
        hash = excluded.hash,
        prev_hash = my_table.hash
    RETURNING
        id,
        hash,      -- will be the new hash
        prev_hash  -- will be the old hash