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