使用函数(不是触发器)的旧值填充 Postgres table

Populate a Postgres table with old values from a function (not a trigger)

我有一个函数可以将唯一列 mytable.valueoldvalue 更新为 newvalue

问题是我想将 oldvalue 与旧的 modified 时间一起存储在单独的存档中 table。

我不知道如何使用普通的 plpgsql 函数 (Postgres 9.6) 来做到这一点。我想我知道如何使用触发器来做到这一点,但我希望仅在某些情况下填充存档 table,因此当显式调用函数时,而不是对 mytable 的每次修改。

create or replace function updatevalue(oldvalue text, newvalue text) returns void
  language plpgsql
  as $$
begin
  update mytable set modified = now(), value = newvalue where value = oldvalue
end
$$;

如何修改上述函数以使用旧 modified 时间填充存档 table?由于可能存在竞争条件,我不想将它作为参数从应用程序传递给函数。

我觉得这个很简单,如果我理解正确的话。

所以,如果你想在 update/function 调用时存档每个旧值及其修改时间,那么你只需要 archive_table 结构:

mytable_pk_value int/bigint -- assumes that mytable have primary key/identity column, lets say "id"
mytable_modified_time -- with  mytable.modified datatype
mytable_oldvalue      -- with  mytable.value datatype

那么在函数中你有:

...
begin
  insert into archive_table 
  (mytable_pk_value, mytable_modified_time, mytable_oldvalue) 
  select id, modified, value 
  from mytable 
  where value = oldvalue;
  update mytable set modified = now(), value = newvalue where value = oldvalue
end
...

关于“可能的竞争条件”
如果你 运行 将 INSERTUPDATE 语句分开,你就会得到你想要避免的东西:竞争条件。 INSERTUPDATE 之间的旧行可能发生任何事情,它没有被锁定。

将两者都包含在事务中(如 建议的那样)不会 解决此问题。您可以使用 FOR UPDATE 锁定行,或者使用数据修改 CTE 仅访问一次行:

WITH upd AS (
   UPDATE mytable n
   SET    modified = now()      -- consider a trigger instead
        , value = $newvalue     -- provide value once
   FROM   mytable o
   WHERE  o.value = $oldvalue   -- provide value once
   AND    n.value = o.value
   RETURNING o.*                -- returns pre-update row
   )
INSERT INTO archive
SELECT * FROM upd;

mytable 中的行仅被访问 一次。没有竞争条件,速度也更快。相关:

  • Return pre-UPDATE Column Values Using SQL Only - PostgreSQL Version

这是假设 mytablearchive 的结构相同,因此我从 INSERT 中省略了目标列列表。如果结构不相同或可以更改,请明确说明并改为拼写列。

你可以把它包装成一个(plpgsql或sql)函数或者直接执行它(作为准备好的语句)。

旁白:我不考虑手动设置 modified,而是考虑使用触发器 ON UPDATEON INSERT OR UPDATE 自动处理此问题:

  • Timestamp can't be saved - postgresql

备选方案:使用 FOR UPDATE

显式锁定
BEGIN;  -- one transaction

INSERT INTO archive
SELECT *
FROM   mytable
WHERE  value = $oldvalue
FOR    UPDATE;            -- lock !

UPDATE mytable
SET    modified = now()
     , value = $newvalue
WHERE  value = $oldvalue;

COMMIT;

如果将其包装在一个函数中,它会自动成为一个事务。

相关: