(作为背景,我使用的是 Postgres 12.4)
我不清楚为什么删除在两个表之间存在循环 FK 并且两个 FK 都设置为 ON DELETE CASCADE 时起作用。
CREATE TABLE a (id bigint PRIMARY KEY);
CREATE TABLE b (id bigint PRIMARY KEY, aid bigint references a(id) on delete cascade);
ALTER TABLE a ADD COLUMN bid int REFERENCES b(id) ON DELETE CASCADE ;
insert into a(id) values (5);
insert into b(id, aid) values (10,5);
update a set bid = 10 where id=5;
DELETE from a where id=5;
我正在考虑的方式是,当您删除表 'a' 中 PK id = 5 的行时,postgres 查看具有引用 a(id) 的引用约束的表,它找到 b,它尝试删除该行在 id = 10 的表 b 中,然后它必须查看引用 b(id) 的表,因此它返回到 a,然后它应该以无限循环结束。但情况似乎并非如此。删除完成且没有错误。
正如一些消息来源在网上所说,情况也并非如此,您无法创建圆形约束。约束创建成功,它们都不是可延迟的。
所以我的问题是 - 为什么 postgres 完成这个循环级联,即使两个约束都没有设置为可延迟,如果它能够这样做,那么即使有一个可延迟选项又有什么意义呢?
最佳答案
外键约束作为系统触发器实现。
对于 ON DELETE CASCADE
,此触发器将运行如下查询:
/* ----------
* The query string built is
* DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
*/
查询运行的是一个新的数据库快照,因此它看不到先前 RI 触发器删除的行:/*
* In READ COMMITTED mode, we just need to use an up-to-date regular
* snapshot, and we will see all rows that could be interesting. But in
* transaction-snapshot mode, we can't change the transaction snapshot. If
* the caller passes detectNewRows == false then it's okay to do the query
* with the transaction snapshot; otherwise we use a current snapshot, and
* tell the executor to error out if it finds any rows under the current
* snapshot that wouldn't be visible per the transaction snapshot. Note
* that SPI_execute_snapshot will register the snapshots, so we don't need
* to bother here.
*/
这可确保 RI 触发器不会再次尝试删除同一行,从而破坏循环。(所有引文均来自
src/backend/utils/adt/ri_triggers.c
。)
关于database - Postgres 中的循环删除级联,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69454969/