级联(ON DELETE / UPDATE)行为的良好解释


98

我不是每天都设计架构,但是当我这样做时,我会尝试正确设置级联更新/删除以简化管理。我了解级联的工作方式,但是我不记得哪个表是哪个表。

例如,如果我有两个表- ParentChild-在Child该引用上具有外键Parent并具有ON DELETE CASCADE,则哪些记录触发级联,而哪些记录被级联删除?我的第一个猜测是Child删除Parent记录后记录会被删除,因为Child记录取决于Parent记录,但是记录ON DELETE不明确。它可能意味着删除Parent记录后删除记录Child,也可能意味着删除Child记录时删除记录Parent。那是什么呢?

我希望语法为ON PARENT DELETE, CASCADEON FOREIGN DELETE, CASCADE或类似的东西来消除歧义。有没有人记得这件事的助记符?

Answers:


138

如果您喜欢ParentChild术语,并且觉得它们很容易被记住,那么您可能希望翻译ON DELETE CASCADELeave No Orphans!

这意味着当Parent删除(杀死)行时,Child表中没有孤立的行保持活动状态。父行的所有子级也将被杀死(删除)。如果这些孩子中的任何一个有孙子孙(通过另一个外键在另一个表中)并且已经ON DELETE CASCADE定义,则这些孩子也应被杀死(以及所有后代,只要定义了级联效应)。

FOREIGN KEY约束本身也可以被描述为Allow No Orphans!(首先)。不Child应该永远被允许(写入)子表,如果它不是Parent(行父表)。

为了保持一致,ON DELETE RESTRICT可以将_转换为_(不那么残酷的)。You Can't Kill Parents!只有无子级的行可以被杀死(删除)。


3
我觉得类比中仍然缺少某些内容。一个孩子不能有一个以上的父母吗?在这种情况下,杀死一名父母会使孩子成为孤儿吗?
Jus12 2014年

7
@ Jus12否,外键约束仅可用于1个父级。这方面不是一个很好的类比。
ypercubeᵀᴹ

1
@ypercube:不允许吗?Order(custID, itemID, orderID)其中,custID引用Customers表中的主键,itemID引用表中的主键Items。不会Order有两个父母吗?
2014年

4
@ Jus12当然可以,但这将是2个外键约束。这样,每个子项(订单)将有一个父项(客户)和一个父项(项目)。但是,这两个FK的行为可能有所不同。(因此,例如,杀死客户会杀死他们所有的(订单)孩子,但是杀死物品不会杀死他们的订单。)
ypercubeᵀᴹ2014年

1
如果我们不说“孤儿”,父母的类比仍然可以工作。如果在子女入境时有两个分别提及两个独立父母的提法,那么仍可以视为离婚夫妇的子女。限制:“我不会让你杀了我的妈妈”级联:“如果你杀了我的父亲,我也会死的”
Christopher McGowan 2015年

31

例如,如果我有两个表-父级和子级-子级记录由父级记录拥有,那么哪个表需要ON DELETE CASCADE?

ON DELETE CASCADE是外键声明中的可选子句。如此这般外键声明。(含义是在“子”表中。)

...这可能意味着删除子记录时删除父记录,或者可能意味着删除父记录时删除子记录。那是什么呢?

解释外键声明的一种方法是:“此列的所有有效值都来自“ that_table”中的“ that_column”。当您删除“子”表中的一行时,没有人在乎。它不会影响数据完整性。

从“ that_table”中删除“父”表中的一行时,会从“子”表的可能值中删除有效值。为了保持数据完整性,您必须对“子”表进行某些操作。级联删除是您可以做的一件事。


2

SQL:2011年规范

有五个选项ON DELETEON UPDATE它们可以应用于FOREIGN KEY。这些<referential actions>直接称为SQL:2011规范

  • ON DELETE CASCADE:如果删除了引用表中的一行,则引用表中所有匹配的行都将被删除。
  • ON DELETE SET NULL:如果删除了引用表的一行,则引用表的所有匹配行中的所有引用列都将设置为null。
  • ON DELETE SET DEFAULT:如果删除了引用表的一行,则引用表的所有匹配行中的所有引用列都将设置为该列的默认值。
  • ON DELETE RESTRICT注意:如果引用表中的行中有匹配的行,则禁止删除该行。
  • ON DELETE NO ACTION (默认值):没有引用删除操作;参照约束仅指定约束检查。

外键建立从属关系。在<referential action>确定何时关系溶解会发生什么。

示例/隐喻/说明

在这个例子中,我们将接受社会和经济的共同模式:每一个地方business是维护的关系的公司bourgeoisie通过fatcat_owner

CREATE TABLE bourgeoisie(
  fatcat_owner varchar(100) PRIMARY KEY
);
INSERT INTO bourgeoisie(fatcat_owner) VALUES
  ( 'Koch Brothers' );

CREATE TABLE business (
  name         varchar(100),
  fatcat_owner varchar(100) REFERENCES bourgeoisie
);
INSERT INTO business(name, fatcat_owner)
  VALUES ('Georgia-Pacific', 'Koch Brothers');

如果所有社会business都受到bourgeoisie其直接影响,fatcat_owner那么在工人革命之后,当您清洗fatcat_owner社会并建立一个无阶级社会时,您会怎么做?

-- Viva la revolución 
BEGIN;
  DELETE FROM bourgeoisie;
END;

您这里有几个选择,

  • 停止革命。用SQL的话来说,RESTRICT。有些人认为这是次要的邪恶,但他们通常是错误的。
  • 允许它继续。如果是这样,那么当革命发生时,SQL为您提供了四个选择,

    • SET NULL-留空。谁知道,也许资本主义得以恢复bourgeoisie,寡头们填补了市场的空白fatcat_owners。重要说明,该列必须为NULLABLE(not NOT NULL),否则将永远不会发生。
    • SET DEFAULT-也许您已经DEFAULT处理了这个问题?一个 DEFAULT可以调用一个函数。也许您的架构已经准备好进行革命了。
    • CASCADE-没有损害控制。如果bourgeoisie继续,则也会business。如果企业必须具有fatcat_pig,那么有时丢失数据比在business表中拥有非企业更有意义。
    • NO ACTION-这本质上是一种延迟检查的方法,在MySQL中与没什么不同RESTRICT,但是在PostgreSQL中,您可以做到

      -- Not a real revolution.
      -- requires constraint be DEFERRABLE INITIALLY DEFERRED
      BEGIN;
        SET CONSTRAINTS ALL DEFERRED;
        DELETE FROM bourgeoisie;
        INSERT INTO bourgeoisie VALUES ( 'Putin' );
        UPDATE business SET fatcat_pig = 'Putin';
      END;
      

      在这样的系统中,仅在事务提交之前验证约束。这可能会导致停止旋转,但是您可以在事务中进行恢复-进行一定程度的“恢复”。


请问referenced表是指父表和referencing表指子表?
sg552

@ sg552是的,您理解正确。
informatik01

0

一个简单的助记符是

父级CASCADE的DELETE删除[通过删除]

这就告诉您哪些删除操作(父级删除操作)级联,ON DELETE CASCADE语句(在子级上)的位置以及删除的内容(子级)。


-3

好吧,也许我们可以合理化语法。让我们来看一个Python示例:

class Parent(self):
    # define parent's fields

class Child(self):    
    # define child's fields
    parent_pk_is_childs_foreign_key = models.ForeignKey(Parent, on_delete=models.CASCADE)

该行显示的是Parent的on_delete(在语句中意外提到),请将删除操作级联到子级。这就是为什么CASCADE语句在子级定义的原因,它标记了需要删除的那些子级

例如,如果您有另一堂课

class GrownUpChild(self):    
        # define grown up child's fields
        parent_pk_is_childs_foreign_key = models.ForeignKey(Parent, on_delete=models.DO_NOTHING)

此结构将清楚地显示哪些儿童需要移走(儿童),哪些儿童需要留下来(GrownUpChild),尽管已成为孤儿

[编辑:在讨论的背景下,特别是在on_delete = models.CASCADE等情况下,]实际上,由于审核和报告的原因以及遗留意外的恢复,离开被删除的父母的孩子通常是理想的行为删除。[当然,企业级软件将围绕这种行为构建,并将已删除的记录标记为Deleted = 1,而不是实际删除它们,并且不减去某些特殊设计的报告,也不会在前端的任何查询中包括它们。另外,它还具有从数据库中清除Deleted == 1记录的功能,通常由UI管理员执行,通常避免数据库管理员方面的任何参与。]


1
“实际上,由于审核和报告的原因,以及删除意外删除,通常留下父母被删除的孩子的子女是一种期望的行为” -在(健全的)数据库中这将是灾难性的。
dezso

@dezso感谢您的输入。但是,多个企业级CRM系统正是这样做的。
乔治·莫吉廖夫斯基'18

TBH并没有使其变得更加明智。我曾经有一个任务来解决这种方法导致的狗屎-除了薪水以外,没有其他乐趣。
dezso

您听起来像是一个精明的数据库管理员:)我完全可以听到您的意思。我上面提到的执行此操作的软件实际上具有手动删除已删除= 1的功能,因此由应用程序的管理员来进行此调用。通常,数据库管理员甚至不参与维护此方面。此外,整个软件的数据库类都是围绕这一概念构建的,因此它始终会在crud操作中检查已删除的标志
George Mogilevsky

是的,这是已知且理智的模式-但是您可能应该更改上面的措词以反映这一点。
dezso
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.