级联删除
外键约束删除有 5 种选项
- CASCADE: 当从父表删除一行时,子表中所有相关的行也会被删除。
- RESTRICT: 当从父表删除一行时,如果子表中存在任何相关的行,则删除操作将被中止。
- SET NULL: 当从父表删除一行时,子表中外键列的值将被设置为 NULL。
- SET DEFAULT: 当从父表删除一行时,子表中外键列的值将被设置为其默认值。
- NO ACTION: 此选项类似于 RESTRICT,但它也可以选择“延迟”到事务结束。这意味着其他级联删除可以先运行,然后此删除约束仅在事务结束时仍然存在引用数据时才会引发错误。
这些选项可以在定义外键约束时使用 "ON DELETE" 子句指定。例如,以下 SQL 语句创建一个带有 CASCADE 选项的外键约束
1alter table child_table2add constraint fk_parent foreign key (parent_id) references parent_table (id)3 on delete cascade;这意味着当从 parent_table 删除一行时,child_table 中的所有相关行也会被删除。
RESTRICT 与 NO ACTION#
NO ACTION 和 RESTRICT 之间的区别很微妙,可能会有些令人困惑。
NO ACTION 和 RESTRICT 都用于防止删除父表中存在相关行的行。但是,它们在行为上存在细微的差异。
当外键约束使用 RESTRICT 选项定义时,这意味着如果删除父表中的一行,数据库将立即引发错误并阻止删除父表中的该行。数据库不会删除、更新或设置为 NULL 引用表中的任何行。
当外键约束使用 NO ACTION 选项定义时,这意味着如果删除父表中的一行,数据库也会引发错误并阻止删除父表中的该行。但是,与 RESTRICT 不同,NO ACTION 具有使用 INITIALLY DEFERRED 延迟检查的选项。这仅会在事务结束时仍然存在引用行时才引发上述错误。
与 RESTRICT 的区别在于,标记为 NO ACTION INITIALLY DEFERRED 的约束将延迟到事务结束,而不是立即运行。例如,如果同一表之间存在另一个标记为 CASCADE 的外键约束,则级联将首先发生并删除引用的行,并且延迟约束不会引发任何错误。否则,如果在事务结束时仍然存在引用父行的行,将像以前一样引发错误。与 RESTRICT 一样,数据库不会删除、更新或设置为 NULL 引用表中的任何行。
在实践中,您可以根据需要使用 NO ACTION 或 RESTRICT。如果您没有指定任何内容,NO ACTION 是默认行为。如果您希望延迟到事务结束进行检查,请使用 NO ACTION INITIALLY DEFERRED。
示例#
让我们通过一个例子进一步说明这种区别。我们将使用以下数据
祖父表
| id | name |
|---|---|
| 1 | 伊丽莎白 |
父表
| id | name | parent_id |
|---|---|---|
| 1 | 查尔斯 | 1 |
| 2 | 戴安娜 | 1 |
子表
| id | name | father | mother |
|---|---|---|---|
| 1 | 威廉 | 1 | 2 |
要创建这些表及其数据,我们运行
1create table grandparent (2 id serial primary key,3 name text4);56create table parent (7 id serial primary key,8 name text,9 parent_id integer references grandparent (id)10 on delete cascade11);1213create table child (14 id serial primary key,15 name text,16 father integer references parent (id)17 on delete restrict18);1920insert into grandparent21 (id, name)22values23 (1, 'Elizabeth');2425insert into parent26 (id, name, parent_id)27values28 (1, 'Charles', 1);2930insert into parent31 (id, name, parent_id)32values33 (2, 'Diana', 1);3435-- We'll just link the father for now36insert into child37 (id, name, father)38values39 (1, 'William', 1);RESTRICT#
RESTRICT 将阻止删除并引发错误
1postgres=# delete from grandparent;2ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"3DETAIL: Key (id)=(1) is still referenced from table "child".即使父表和祖父表之间的外键约束为 CASCADE,子表和父表之间的约束为 RESTRICT。因此,引发错误并且未删除任何记录。
NO ACTION#
让我们将子表-父表关系更改为 NO ACTION
1alter table child2drop constraint child_father_fkey;34alter table child5add constraint child_father_fkey foreign key (father) references parent (id)6 on delete no action;我们看到 NO ACTION 也会阻止删除并引发错误
1postgres=# delete from grandparent;2ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"3DETAIL: Key (id)=(1) is still referenced from table "child".NO ACTION INITIALLY DEFERRED#
我们将子表和父表之间的外键约束更改为 NO ACTION INITIALLY DEFERRED
1alter table child2drop constraint child_father_fkey;34alter table child5add constraint child_father_fkey foreign key (father) references parent (id)6 on delete no action initially deferred;您会看到 INITIALLY DEFFERED 似乎像 NO ACTION 或 RESTRICT 一样运行。当我们运行删除时,似乎没有任何区别
1postgres=# delete from grandparent;2ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"3DETAIL: Key (id)=(1) is still referenced from table "child".但是,当它与其他约束结合使用时,任何其他约束都优先。例如,让我们运行相同的操作,但添加一个具有 CASCADE 删除的 mother 列
1alter table child2add column mother integer references parent (id)3 on delete cascade;45update child6set mother = 27where id = 1;然后让我们在 grandparent 表上运行删除
1postgres=# delete from grandparent;2DELETE 134postgres=# select * from parent;5 id | name | parent_id6----+------+-----------7(0 rows)89postgres=# select * from child;10 id | name | father | mother11----+------+--------+--------12(0 rows)mother 删除优先于 father,因此威廉被删除了。威廉被删除后,没有引用“查尔斯”的引用,因此他可以被删除,即使之前没有(没有 INITIALLY DEFERRED)。就像 RESTRICT 一样,数据库不会删除、更新或设置为 NULL 引用表中的任何行。