在 SQL Server 中设置外键时,在什么情况下应该在删除或更新时级联,其背后的原因是什么?
这可能也适用于其他数据库。
我最关注的是每种场景的具体示例,最好是来自成功使用它们的人。
在 SQL Server 中设置外键时,在什么情况下应该在删除或更新时级联,其背后的原因是什么?
这可能也适用于其他数据库。
我最关注的是每种场景的具体示例,最好是来自成功使用它们的人。
到目前为止我所看到的总结:
外键是确保数据库引用完整性的最佳方式。由于魔法而避免级联就像在汇编中编写所有内容一样,因为您不相信编译器背后的魔法。
不好的是错误使用外键,例如反向创建它们。
Juan Manuel 的示例是典型示例,如果您使用代码,则更有可能在数据库中留下虚假的 DocumentItems,它们会来咬您。
级联更新很有用,例如,当您通过可以更改的内容引用数据时,例如用户表的主键是名称,姓氏组合。然后,您希望该组合中的更改传播到它们被引用的任何地方。
@Aidan,您提到的这种清晰度需要付出高昂的代价,在您的数据库中留下虚假数据的机会并不小。对我来说,通常只是对 DB 不熟悉,并且在与 DB 合作之前无法找到哪些 FK 会导致这种恐惧。要么,要么不断滥用级联,在实体在概念上不相关的地方使用它,或者你必须保留历史。
我从不使用级联删除。
如果我想从数据库中删除一些东西,我想明确地告诉数据库我想要取出什么。
当然,它们是数据库中可用的功能,有时可以使用它们,例如,如果您有一个“订单”表和一个“订单项”表,您可能希望在删除一个命令。
我喜欢通过代码(或存储过程)而不是“魔术”发生而获得的清晰度。
出于同样的原因,我也不喜欢触发器。
需要注意的是,如果您确实删除了“订单”,即使级联删除已删除 50 个“订单项”,您也会收到“受影响的 1 行”报告。
我经常使用级联删除。
知道对数据库工作的人可能永远不会留下任何不需要的数据,这感觉很好。如果依赖关系增加,我只需在 Management Studio 中更改图表中的约束,而不必调整 sp 或 dataacces。
也就是说,我在级联删除和循环引用方面有 1 个问题。这通常会导致数据库的某些部分没有级联删除。
我做了很多数据库工作,很少发现级联删除有用。我有效地使用它们的一次是在一个由夜间工作更新的报告数据库中。我通过删除自上次导入以来已更改的所有顶级记录,然后重新导入修改后的记录以及与它们相关的任何内容,确保正确导入任何更改的数据。它使我不必编写许多从数据库底部到顶部的复杂删除。
我不认为级联删除与触发器一样糟糕,因为它们只删除数据,触发器内部可能包含各种讨厌的东西。
一般来说,我完全避免真正的删除,而是使用逻辑删除(即有一个名为 isDeleted 的位列设置为 true)。
一个示例是当您在实体之间存在依赖关系时......即:Document -> DocumentItems(当您删除 Document 时,DocumentItems 没有存在的理由)
如果删除了引用 PK 记录,则在您希望删除带有 FK 的记录的地方使用级联删除。换句话说,如果没有引用记录,记录就毫无意义。
我发现级联删除对于确保默认删除死引用而不是导致空异常很有用。
当您希望删除子表中的行时如果在父表中删除相应的行。
如果未使用on cascade delete,则会为引用完整性引发错误。
当您希望在外键中更新主键的更改时
我听说过 DBA 和/或“公司政策”纯粹因为过去的糟糕经历而禁止使用“On Delete Cascade”(和其他)。在一个案例中,一个人写了三个触发器,最终相互调用。三天的恢复导致完全禁止触发器,这一切都是因为一个 idjit 的行为。
当然,有时需要触发器而不是“删除级联”,例如需要保留一些子数据时。但在其他情况下,使用 On Delete 级联方法完全有效。“On Delete cascade”的一个关键优势是它捕获了所有的孩子;如果编码不正确,自定义编写的触发器/存储过程可能不会。
我认为应该允许开发人员根据开发内容和规范说明做出决定。基于糟糕体验的地毯禁令不应成为标准;“从不使用”的思维过程充其量是严厉的。每次都需要做出判断,并随着商业模式的变化而做出改变。
这不就是发展的意义吗?
进行级联删除(而不是在代码中进行)的一个原因是为了提高性能。
案例 1:使用级联删除
DELETE FROM table WHERE SomeDate < 7 years ago;
案例 2:没有级联删除
FOR EACH R IN (SELECT FROM table WHERE SomeDate < 7 years ago) LOOP
DELETE FROM ChildTable WHERE tableId = R.tableId;
DELETE FROM table WHERE tableId = R.tableid;
/* More child tables here */
NEXT
其次,当您使用级联删除添加额外的子表时,案例 1 中的代码将继续工作。
我只会在关系的语义是“一部分”的情况下放入级联。否则一些白痴会在你这样做时删除一半的数据库:
DELETE FROM CURRENCY WHERE CurrencyCode = 'USD'
我尽量避免在 SQL Server 中没有明确请求的删除或更新。
通过级联或使用触发器。他们往往会在一段时间后咬你一口,无论是在试图追踪错误或诊断性能问题时。
我会使用它们的地方是保证一致性,而不是太多的努力。要获得相同的效果,您必须使用存储过程。
我和这里的其他人一样,发现级联删除实际上只有一点点帮助(删除其他表中的引用数据实际上并没有那么多工作——如果有很多表,你只需使用脚本自动执行此操作)但真的很烦人当有人不小心级联删除了一些难以恢复的重要数据时。
我会使用的唯一情况是表中的数据是高度控制的(例如,有限的权限),并且只能通过已验证的受控过程(如软件更新)进行更新或删除。
可以通过以下三种方式之一来处理对 S 的删除或更新,即移除在 R 的某些元组中发现的外键值:
传播称为级联。
有两种情况:
‣ 如果 S 中的元组被删除,则删除引用它的 R 元组。
‣ 如果 S 中的元组已更新,则更新引用它的 R 元组中的值。
如果您正在使用具有不同版本的许多不同模块的系统,如果级联删除的项目是 PK 持有者的一部分/拥有,这将非常有帮助。否则,在删除 PK 所有者之前,所有模块都需要立即修补以清理其依赖项,否则将完全省略外键关系,如果清理不正确,可能会在系统中留下大量垃圾。
我刚刚为两个已经存在的表之间的新交集表引入了级联删除(仅删除的交集),在很长一段时间内不鼓励级联删除之后。如果数据丢失也不错。
然而,对于类似枚举的列表表来说,这是一件坏事:有人从表“颜色”中删除条目 13 - 黄色,并且数据库中的所有黄色项目都被删除。此外,这些有时会以 delete-all-insert-all 的方式进行更新,导致完全省略参照完整性。当然这是错误的,但是你将如何改变一个已经运行多年的复杂软件,而引入真正的参照完整性可能会带来意想不到的副作用?
另一个问题是,即使在删除主键后,仍应保留原始外键值。可以为原始 FK 创建一个 tombstone 列和一个 ON DELETE SET NULL 选项,但这又需要触发器或特定代码来维护冗余(PK 删除后除外)键值。
在物理数据库中实现逻辑超类型和子类型实体时,级联删除非常有用。
当单独的超类型和子类型表用于物理实现超类型/子类型(而不是将所有子类型属性汇总到单个物理超类型表中)时,存在一对一-这些表之间的关系,然后问题就变成了如何在这些表之间保持主键 100% 同步。
级联删除可能是一个非常有用的工具:
1) 确保删除一个超类型记录也会删除相应的单个子类型记录。
2) 确保对子类型记录的任何删除也会删除超类型记录。这是通过在子类型表上实现“替代”删除触发器来实现的,该触发器会删除相应的超类型记录,进而级联删除子类型记录。
以这种方式使用级联删除可确保不存在孤立的超类型或子类型记录,无论您是先删除超类型记录还是先删除子类型记录。