在PostgreSQL 9.6及更高版本中,定义将执行的触发器函数的正确方法是什么 每当插入因唯一性约束而失败时更新?
我知道写insert ... on conflict ... do update set ...
语句是直截了当的,但是我的
我的想法是,我希望有一些表将重复插入视为更新;否则那件
逻辑必须由应用程序处理,而不是数据库。
我找到的一个解决方案就是:
create table versions (
key text primary key,
version text );
/* ### TAINT not sure whether there may be race conditions with this upsert trigger */
create function on_before_insert_versions() returns trigger language plpgsql volatile as $$ begin
if exists ( select 1 from versions where key = new.key ) then
update versions set version = new.version where key = new.key;
return null;
end if;
return new;
end; $$;
create trigger on_before_insert_versions
before insert on versions for each row execute procedure on_before_insert_versions();
insert into versions values
( 'server', '3.0.3' ),
( 'api', '2' );
insert into versions values
( 'api', '3' );
select * from versions;
key | version
--------+---------
server | 3.0.3
api | 3
但是,不是触发竞争条件的触发器吗?我试过用一个
触发器中的insert ... on conflict ... do update set ...
语句,但当然失败了
因为它会触发触发功能本身,导致无限回归。
我还尝试使用一对alter table ... disable trigger ...
/ enable
语句,但是
错误cannot ALTER TABLE ... because it is being used by active queries in this session
。
始终执行更新而不是插入唯一性约束的规范表单是什么 PostgreSQL中的违规行为?
更新 - PostgreSQL中的插件,或者他们长时间缺席,都是一个热门话题,并且经常提出许多不那么完美的解决方案。
鉴于Postgres维护者花了很多时间和精力让insert ... on conflict .. do update
在没有竞争条件的情况下工作,所以接受一个“似乎有用”的自制解决方案(直到它没有)可能是不明智的。
当我写下我的问题时,我坚持要求insert
触发器在冲突中执行update
; PostgreSQL没有很好地支持这一点,主要问题是你在insert
触发器内的同一个表上执行的before insert
将导致调用相同的触发器。 @Laurenz Albe建议如何摆脱无限循环,虽然提出的技术(巧妙!)看起来是一件好事,但我们不知道对性能或其他副作用可能产生的影响。
最后,@ Ilya Dyoshin提出要从包含必要SQL逻辑的应用程序调用函数。我觉得这是一个双赢的解决方案,因为
1)它不会将表格insert into x
的{{1}}的语义更改为“真正意味着x
,有时会更改”;
2)'upsert语义'在应用程序代码中是明确的,但没有详细说明;
3)你可以仍然做update
而不打算隐含'更新' - 事后看来,这可能是最重要的考虑因素。
答案 0 :(得分:3)
我同意Ilya的说法,在应用程序中以直接的方式执行此操作会更好。
但我是在思想实验的精神中接受它,我的解决方案使用pg_trigger_depth()
的力量来逃避无休止的递归:
CREATE OR REPLACE FUNCTION on_before_insert_versions() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
IF pg_trigger_depth() = 1 THEN
INSERT INTO versions (key, version) VALUES (NEW.key, NEW.version)
ON CONFLICT (key)
DO UPDATE SET version = NEW.version;
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;$$;
您的解决方案肯定容易受到竞争条件的影响:两个并发INSERT可能导致同时运行的触发器,这两个触发器都无法在versions
中找到匹配的行,从而导致INSERT,其中一个必须失败。
答案 1 :(得分:1)
最好使用纯upsert。
否则你可以引入更复杂的逻辑,不要从触发器返回插入数据(在插入不返回值之前读取docs = if触发器,不执行插入)