Tom*_*ský 12 postgresql triggers locking
更新:我从问题中消除了休眠。我完全重新编写了对问题的描述,以尽可能地简化它。
我有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
Run Code Online (Sandbox Code Playgroud)
在这种设置中,我使用打开两个终端窗口,psql
然后执行以下步骤:
begin;
update master set detail_id=null, name='y' where id=1000;
Run Code Online (Sandbox Code Playgroud)
begin;
set statement_timeout = 4000;
insert into detail_candidate (master_id, detail_id) values (1000, 1);
Run Code Online (Sandbox Code Playgroud)
第二个终端超时中的最后一条命令,带有消息
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"
Run Code Online (Sandbox Code Playgroud)
我的观察和问题(更改是独立的):
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
没有根据触发条件单独选择模式,以及为什么触发条件导致了它。
有趣的问题。这是我最好的猜测。我没有测试过。
一般来说,Postgres 对语句对数据产生什么影响的有根据的猜测不会扩展到触发逻辑。当执行第二条语句时,postgres 看到外键约束,并且知道它必须检查分配(插入)的值是否有效,即它是否代表外表中的有效键。无论实践多么糟糕,触发器都有可能对所提议的外键的有效性产生影响(例如,如果触发器删除记录)。
(情况 1)如果没有触发器,则它可以查看数据(预提交和暂存提交)并确定建议的值是否保证有效。(情况2)如果没有FK约束,那么触发器不会影响插入的有效性,因此是允许的。(情况 3)如果省略detail_id=null
,则更新中没有任何更改,因此触发器不会触发,因此它的存在无关紧要。
我尽可能避免 FK 约束和触发器。在我看来,最好是让数据库意外地包含部分不正确的数据,然后让它完全挂起,就像您在这里看到的那样。我会删除所有 FK 约束和触发器,并强制所有更新和插入操作通过存储函数进行操作,这些函数在开始/提交锁内执行验证,并立即适当地处理不正确/无效的插入/更新尝试,而不是强制 postgres等待命令 1 提交,然后再决定是否允许命令 2。
编辑:看到这个问题
编辑2:我能找到的关于触发器相对于约束检查的时间的官方文档最接近的内容是来自触发器文档
可以指定触发器在尝试对行进行操作之前触发(在检查约束并尝试 INSERT、UPDATE 或 DELETE 之前);或操作完成后(检查约束并完成 INSERT、UPDATE 或 DELETE 后);或者代替操作(在视图上插入、更新或删除的情况下)。如果触发器在事件之前或代替事件触发,则触发器可以跳过当前行的操作,或更改正在插入的行(仅适用于 INSERT 和 UPDATE 操作)。
这有点不清楚,约束检查之前发生的触发是否适用于其他事务的约束检查。不管怎样,这个问题要么是一个错误,要么是记录不足。
归档时间: |
|
查看次数: |
336 次 |
最近记录: |