更新:我从问题中消除了休眠。我完全重新编写了对问题的描述,以尽可能地简化它。
我有master
带noop触发器的detail
表和带master
和detail
表之间的两个关系的表:
create table detail (
id bigint not null,
code varchar(255) not null,
primary key (id)
);
create table master (
id bigint not null,
name varchar(255),
detail_id bigint, -- "preferred" detail is one-to-one relation
primary key (id),
unique (detail_id),
foreign key (detail_id) references detail(id)
);
create table detail_candidate ( -- "candidate" details = many-to-many relation modeled as join table
master_id bigint not null,
detail_id bigint not null,
primary key (master_id, detail_id),
foreign key (detail_id) references detail(id),
foreign key (master_id) references master(id)
);
create or replace function trgf() returns trigger as $$
begin
return NEW;
end;
$$ language 'plpgsql';
create trigger trg
before insert or update
on master
for each row execute procedure trgf();
insert into master (id, name) values (1000, 'x'); -- this is part of database setup
insert into detail (code, id) values ('a', 1); -- this is part of database setup
在这种设置中,我使用打开两个终端窗口,psql
然后执行以下步骤:
在第一个终端中,更改主服务器(将事务保持打开状态)
begin;
update master set detail_id=null, name='y' where id=1000;
在第二终端中,添加详细信息候选者以自己进行交易
begin;
set statement_timeout = 4000;
insert into detail_candidate (master_id, detail_id) values (1000, 1);
第二个终端超时中的最后一条命令,带有消息
ERROR: canceling statement due to statement timeout CONTEXT: while locking tuple (0,1) in relation "master" SQL statement "SELECT 1 FROM ONLY "public"."master" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR KEY SHARE OF x"
我的观察和问题(更改是独立的):
当数据库设置为无触发时,即drop trigger trg on master;
在初始设置后调用时,一切正常。为什么存在noop触发器会产生这种影响?我不明白
当数据库被设置为没有唯一约束时master.detail_id
(即alter table master drop constraint master_detail_id_key;
在初始设置后被调用),一切也都可以正常工作。为什么?
当我detail=null
在第一个终端的update语句中省略显式分配时(因为无论如何安装程序中都存在null值),一切也都正常。为什么?
在Postgres 9.6.12(嵌入式),9.6.15(在Docker中),11.5(在Docker中)中试用。
问题tomaszalusky/trig-example
可以在DockerHub上可用的Docker映像中重现,也可以从此Dockerfile(内部指令)构建。
更新2:我发现上面三个观察的常见行为。我在第二个事务中select * from pgrowlocks('master')
从pgrowlocks扩展中生成了查询。该行级锁更新行的master
是FOR UPDATE
在失败的情况下,但FOR NO KEY UPDATE
在所有三个工作情况。这是与文档中的模式匹配表完全一致的,因为FOR UPDATE
mode是更强大的模式,而insert语句请求的mode是FOR KEY SHARE
(从错误消息中可以明显看出,也调用该select ... for key share
命令具有与command相同的效果insert
)。
FOR UPDATE
模式文档说明:
FOR UPDATE锁定模式还可以通过(...)UPDATE来修改,该UPDATE会修改某些列上的值。当前,在UPDATE情况下考虑的那组列是可以在外键(...)中使用的唯一索引。
master.detail_id
列是正确的。但是,仍然不清楚为什么FOR UPDATE
没有根据触发条件单独选择模式,以及为什么触发条件导致了它。