级联删除
外键约束删除有5种选项:
- CASCADE(级联删除): 当父表中的行被删除时,子表中所有关联行也会被删除。
- RESTRICT(限制删除): 当父表中的行被删除时,如果子表中存在关联行,则删除操作会被中止。
- SET NULL(设为空值): 当父表中的行被删除时,子表中外键列的值会被设为NULL。
- SET DEFAULT(设为默认值): 当父表中的行被删除时,子表中外键列的值会被设为其默认值。
- NO ACTION(无动作): 该选项与RESTRICT类似,但可以选择"延迟"到事务结束时检查。这意味着其他级联删除可以先执行,只有当事务结束时仍有被引用的数据存在时才会抛出错误。
这些选项可以在定义外键约束时使用"ON DELETE"子句指定。例如,以下SQL语句创建了一个使用CASCADE
选项的外键约束:
123alter table child_tableadd constraint fk_parent foreign key (parent_id) references parent_table (id) on delete cascade;
这意味着当从parent_table
中删除一行时,child_table
中所有关联行也会被删除。
RESTRICT
与 NO ACTION
的区别
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
。
示例
让我们通过一个示例进一步说明两者的区别。我们将使用以下数据:
grandparent
(祖父母表)
id | name |
---|---|
1 | Elizabeth |
parent
(父母表)
id | name | parent_id |
---|---|---|
1 | Charles | 1 |
2 | Diana | 1 |
child
(子女表)
id | name | father | mother |
---|---|---|---|
1 | William | 1 | 2 |
创建这些表及其数据的SQL语句如下:
123456789101112131415161718192021222324252627282930313233343536373839create table grandparent ( id serial primary key, name text);create table parent ( id serial primary key, name text, parent_id integer references grandparent (id) on delete cascade);create table child ( id serial primary key, name text, father integer references parent (id) on delete restrict);insert into grandparent (id, name)values (1, 'Elizabeth');insert into parent (id, name, parent_id)values (1, 'Charles', 1);insert into parent (id, name, parent_id)values (2, 'Diana', 1);-- 目前我们只关联父亲insert into child (id, name, father)values (1, 'William', 1);
RESTRICT
约束
RESTRICT
会阻止删除操作并抛出错误:
123postgres=# delete from grandparent;ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"DETAIL: Key (id)=(1) is still referenced from table "child".
尽管父母表与祖父母表之间的外键约束是CASCADE
,但子女表与父亲之间的约束是RESTRICT
。因此会抛出错误且不会删除任何记录。
设置 NO ACTION
约束
让我们将子表-父表关系改为 NO ACTION
约束:
123456alter table childdrop constraint child_father_fkey;alter table childadd constraint child_father_fkey foreign key (father) references parent (id) on delete no action;
可以看到 NO ACTION
同样会阻止删除操作并报错:
123postgres=# delete from grandparent;ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"DETAIL: Key (id)=(1) is still referenced from table "child".
NO ACTION INITIALLY DEFERRED
我们将修改子表与父表之间的外键约束为NO ACTION INITIALLY DEFERRED
:
123456alter table childdrop constraint child_father_fkey;alter table childadd constraint child_father_fkey foreign key (father) references parent (id) on delete no action initially deferred;
这里你会发现INITIALLY DEFFERED
的行为似乎与NO ACTION
或RESTRICT
类似。当我们执行删除操作时,似乎没有区别:
123postgres=# delete from grandparent;ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"DETAIL: Key (id)=(1) is still referenced from table "child".
但是,当与其他约束结合使用时,其他约束会优先处理。例如,我们执行相同操作,但添加一个具有CASCADE
删除的mother
列:
1234567alter table childadd column mother integer references parent (id) on delete cascade;update childset mother = 2where id = 1;
然后对grandparent
表执行删除:
123456789101112postgres=# delete from grandparent;DELETE 1postgres=# select * from parent; id | name | parent_id----+------+-----------(0 rows)postgres=# select * from child; id | name | father | mother----+------+--------+--------(0 rows)
mother
列的删除操作优先于father
列,因此William被删除了。在William被删除后,就不再存在对"Charles"的引用,所以即使之前不能删除(没有INITIALLY DEFERRED
),现在也可以自由删除了。