数据库

级联删除


外键约束删除有 5 种选项

  1. CASCADE: 当从父表删除一行时,子表中所有相关的行也会被删除。
  2. RESTRICT: 当从父表删除一行时,如果子表中存在任何相关的行,则删除操作将被中止。
  3. SET NULL: 当从父表删除一行时,子表中外键列的值将被设置为 NULL。
  4. SET DEFAULT: 当从父表删除一行时,子表中外键列的值将被设置为其默认值。
  5. NO ACTION: 此选项类似于 RESTRICT,但它也可以选择“延迟”到事务结束。这意味着其他级联删除可以先运行,然后此删除约束仅在事务结束时仍然存在引用数据时才会引发错误。

这些选项可以在定义外键约束时使用 "ON DELETE" 子句指定。例如,以下 SQL 语句创建一个带有 CASCADE 选项的外键约束

1
alter table child_table
2
add constraint fk_parent foreign key (parent_id) references parent_table (id)
3
on delete cascade;

这意味着当从 parent_table 删除一行时,child_table 中的所有相关行也会被删除。

RESTRICTNO ACTION#

NO ACTIONRESTRICT 之间的区别很微妙,可能会有些令人困惑。

NO ACTIONRESTRICT 都用于防止删除父表中存在相关行的行。但是,它们在行为上存在细微的差异。

当外键约束使用 RESTRICT 选项定义时,这意味着如果删除父表中的一行,数据库将立即引发错误并阻止删除父表中的该行。数据库不会删除、更新或设置为 NULL 引用表中的任何行。

当外键约束使用 NO ACTION 选项定义时,这意味着如果删除父表中的一行,数据库也会引发错误并阻止删除父表中的该行。但是,与 RESTRICT 不同,NO ACTION 具有使用 INITIALLY DEFERRED 延迟检查的选项。这仅会在事务结束时仍然存在引用行时才引发上述错误。

RESTRICT 的区别在于,标记为 NO ACTION INITIALLY DEFERRED 的约束将延迟到事务结束,而不是立即运行。例如,如果同一表之间存在另一个标记为 CASCADE 的外键约束,则级联将首先发生并删除引用的行,并且延迟约束不会引发任何错误。否则,如果在事务结束时仍然存在引用父行的行,将像以前一样引发错误。与 RESTRICT 一样,数据库不会删除、更新或设置为 NULL 引用表中的任何行。

在实践中,您可以根据需要使用 NO ACTIONRESTRICT。如果您没有指定任何内容,NO ACTION 是默认行为。如果您希望延迟到事务结束进行检查,请使用 NO ACTION INITIALLY DEFERRED

示例#

让我们通过一个例子进一步说明这种区别。我们将使用以下数据

祖父表

idname
1伊丽莎白

父表

idnameparent_id
1查尔斯1
2戴安娜1

子表

idnamefathermother
1威廉12

要创建这些表及其数据,我们运行

1
create table grandparent (
2
id serial primary key,
3
name text
4
);
5
6
create table parent (
7
id serial primary key,
8
name text,
9
parent_id integer references grandparent (id)
10
on delete cascade
11
);
12
13
create table child (
14
id serial primary key,
15
name text,
16
father integer references parent (id)
17
on delete restrict
18
);
19
20
insert into grandparent
21
(id, name)
22
values
23
(1, 'Elizabeth');
24
25
insert into parent
26
(id, name, parent_id)
27
values
28
(1, 'Charles', 1);
29
30
insert into parent
31
(id, name, parent_id)
32
values
33
(2, 'Diana', 1);
34
35
-- We'll just link the father for now
36
insert into child
37
(id, name, father)
38
values
39
(1, 'William', 1);

RESTRICT#

RESTRICT 将阻止删除并引发错误

1
postgres=# delete from grandparent;
2
ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"
3
DETAIL: Key (id)=(1) is still referenced from table "child".

即使父表和祖父表之间的外键约束为 CASCADE,子表和父表之间的约束为 RESTRICT。因此,引发错误并且未删除任何记录。

NO ACTION#

让我们将子表-父表关系更改为 NO ACTION

1
alter table child
2
drop constraint child_father_fkey;
3
4
alter table child
5
add constraint child_father_fkey foreign key (father) references parent (id)
6
on delete no action;

我们看到 NO ACTION 也会阻止删除并引发错误

1
postgres=# delete from grandparent;
2
ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"
3
DETAIL: Key (id)=(1) is still referenced from table "child".

NO ACTION INITIALLY DEFERRED#

我们将子表和父表之间的外键约束更改为 NO ACTION INITIALLY DEFERRED

1
alter table child
2
drop constraint child_father_fkey;
3
4
alter table child
5
add constraint child_father_fkey foreign key (father) references parent (id)
6
on delete no action initially deferred;

您会看到 INITIALLY DEFFERED 似乎像 NO ACTIONRESTRICT 一样运行。当我们运行删除时,似乎没有任何区别

1
postgres=# delete from grandparent;
2
ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"
3
DETAIL: Key (id)=(1) is still referenced from table "child".

但是,当它与其他约束结合使用时,任何其他约束都优先。例如,让我们运行相同的操作,但添加一个具有 CASCADE 删除的 mother

1
alter table child
2
add column mother integer references parent (id)
3
on delete cascade;
4
5
update child
6
set mother = 2
7
where id = 1;

然后让我们在 grandparent 表上运行删除

1
postgres=# delete from grandparent;
2
DELETE 1
3
4
postgres=# select * from parent;
5
id | name | parent_id
6
----+------+-----------
7
(0 rows)
8
9
postgres=# select * from child;
10
id | name | father | mother
11
----+------+--------+--------
12
(0 rows)

mother 删除优先于 father,因此威廉被删除了。威廉被删除后,没有引用“查尔斯”的引用,因此他可以被删除,即使之前没有(没有 INITIALLY DEFERRED)。就像 RESTRICT 一样,数据库不会删除、更新或设置为 NULL 引用表中的任何行。