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

例如,如果我有两个表-ParentChild-在Child上具有引用Parent并具有ON DELETE CASCADE的外键,该记录会触发级联,而哪些记录会被级联删除?我的第一个猜测是删除Child记录时删除Parent记录,因为Child记录取决于Parent记录,但是ON DELETE模棱两可;删除Parent记录时可能意味着删除Child记录,或者删除Child时可能意味着删除Parent记录。

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

#1 楼

如果您喜欢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!转换为)q4312079q只能杀死(删除的)没有孩子的行。

评论


我觉得这个类比中还缺少一些东西。一个孩子不能有一个以上的父母吗?在这种情况下,杀死一名父母会使孩子成为孤儿吗?

– Jus12
2014年11月6日下午13:31

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

–超立方体ᵀᴹ
2014年11月6日13:33



@ypercube:不允许吗? Order(custID,itemID,orderID),其中ustDID指Customers表中的主键,而itemID指Items表中的主键。订单不会有两个父母吗?

– Jus12
2014年11月6日15:32

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

–超立方体ᵀᴹ
2014年11月6日15:45

如果我们不说“孤儿”,父母的类比仍然可以工作。如果在孩子入场券上有两个分别提到两个独立父母的提法,那么仍可以视为离婚夫妇的孩子。限制:“我不会让你杀死我的母亲”级联:“如果你杀死我的父亲,我也会死”

–克里斯托弗·麦克高恩(Christopher McGowan)
15年2月28日在18:15

#2 楼


例如,如果我有两个表-Parent和Child-其中Child
记录由Parent记录拥有,则哪个表需要ON DELETE
CASCADE?


ON DELETE CASCADE是外键声明中的可选子句。外键声明也是如此。 (意味着,在“子”表中。)


...这可能意味着删除子记录时删除了父记录
,或者可能意味着删除了删除父级后的子级记录。


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

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


从PostgreSQL文档开始。



限制和级联删除是两个最常见的选择。
RESTRICT防止删除被引用的行。 NO ACTION表示
,如果在检查约束时仍存在任何引用行,则会引发
错误;如果您未指定
任何内容,则这是默认行为。 (这两个选择之间的本质区别是,
NO ACTION允许将支票推迟到
事务中的后面,而RESTRICT不允许。)CASCADE指定当
引用行是删除后,引用该行的行也应该被自动删除。还有其他两个选项:SET NULL
和SET DEFAULT。当删除被引用的行时,这会导致
引用行中的引用列分别设置为null或它们的默认值。请注意,这些确实
不要原谅您遵守任何约束条件。例如,如果
操作指定SET DEFAULT,但是默认值不能满足
外键约束,则该操作将失败。


评论


谢谢,但是您还能做什么?

–leeCoder
1月4日15:47

@leeCoder:更新了答案,并提供了对文档的引用。

–迈克·谢里尔(Mike Sherrill)的“猫召回”
1月4日18:59

#3 楼

SQL:2011 Spec
ON DELETEON UPDATE有五个选项可应用于FOREIGN KEY。它们直接从SQL:2011规范中称为<referential actions>ON DELETE CASCADE:如果删除了被引用表的一行,则将删除引用表中的所有匹配行。

ON DELETE SET NULL:如果引用表的一行被删除,则引用表的所有匹配行中的所有引用列都将设置为null。

ON DELETE SET DEFAULT:如果删除引用表的一行,然后将引用表的所有匹配行中的所有引用列都设置为该列的默认值。

ON DELETE RESTRICT:禁止删除引用的行

ON DELETE NO ACTION(默认值):没有引用删除操作;请参见参考。引用约束仅指定约束检查。


外键建立依赖关系。
示例/隐喻/说明
在此示例中,我们将接受社会和经济的通用模型:每个<referential action>都是一家维持与businessbourgeoisie
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');

如果所有fatcat_owner都通过business直接受到bourgeoisie的影响,那么当您清洗fatcat_owner并没有阶级时,工人革命后该怎么办?社会吗?
-- Viva la revolución 
BEGIN;
  DELETE FROM bourgeoisie;
END;

这里有一些选择,


停止革命。用SQL的说法是fatcat_owner。有些人认为这是次要的邪恶,但通常是错误的。


让它继续下去。如果是这样,那么当革命发生时,SQL为您提供了四个选择,


RESTRICT-将其留空。谁知道,也许资本主义得以恢复,寡头主义者填补了SET NULL的席位。重要说明,该列必须是bourgeoisie(而不是fatcat_owners),否则将永远不会发生。 A
NULLABLE可以调用一个函数。也许您的方案已经准备好进行革命了。


NOT NULL-没有损坏控制。如果SET DEFAULT前进,则DEFAULT也会前进。如果企业必须具有DEFAULT,那么有时丢失数据比在CASCADE表中没有业务更有意义。


bourgeoisie -这本质上是一种方法延迟检查的时间,在MySQL中与business没什么不同,但是在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_owner = 'Putin';
    END;

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






评论


被引用表是否表示父表,而引用表是否表示子表?

– sg552
18年11月11日在17:19

@ sg552是的,您理解正确。

–informatik01
19年5月16日在21:58

#4 楼

一个简单的助记符将是

父级CASCADE的ON DELETE [通过删除]这里

,它告诉您级联哪些删除项(父级的DELETE),在ON DELETE CASCADE所在的位置语句(在子项上),删除的内容(在子项上)。

#5 楼

好吧,也许我们可以使语法合理化。让我们举一个Python示例:这就是为什么在子级定义CASCADE语句的原因,它标记了需要删除的子级

,例如,如果您有另一个类

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)


此结构将清楚地显示尽管孤儿已成为孤儿,但哪些孩子仍需移走(Child),哪些孩子仍留在(GrownUpChild)

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

评论


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

– dezso
18年7月20日在10:22

@dezso感谢您的输入。但是,多个企业级CRM系统正是这样做的。

–乔治·莫吉廖夫斯基
18年7月23日在2:22

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

–乔治·莫吉廖夫斯基
18年7月24日在14:38

是的,这是一个已知且理智的模式-但是您可能应该更改上面的措词以反映这一点。

– dezso
18年7月25日在9:09