何时/为何在 SQLServer 中使用级联?

在 SQLServer 中设置外键时,在什么情况下应该对删除或更新进行级联,其背后的原因是什么?

这可能也适用于其他数据库。

我正在寻找最具体的例子,每个场景,最好是从某人谁已经成功地使用它们。

109240 次浏览

一个例子是,当实体之间存在依赖关系时... 例如: Document-> DocumentItems (当删除 Document 时,DocumentItems 没有存在的理由)

我尽量避免删除或更新 SQL 服务器中没有明确请求的内容。

无论是通过级联还是通过使用触发器。无论是在试图追踪 bug 还是在诊断性能问题时,它们都会在某些时候给你带来麻烦。

我使用它们的地方在于保证一致性,而不需要太多的努力。要获得同样的效果,必须使用存储过程。

我从不使用级联删除。

如果我想从数据库中删除一些内容,我想显式地告诉数据库我想删除的内容。

当然,它们是数据库中可用的函数,有时可以使用它们,例如,如果您有一个“ order”表和一个“ orderItem”表,您可能希望在删除订单时清除这些项。

我喜欢在代码(或存储过程)中进行操作所获得的清晰性,而不是“神奇”的发生。

出于同样的原因,我也不喜欢触发器。

需要注意的是,如果您删除了一个“ order”,即使级联删除删除了50个“ orderItem”,您也会收到“受影响的1行”报告。

外键是确保数据库参照完整性的最佳方式。因为神奇而避免级联就像用汇编写所有东西一样,因为你不信任编译器背后的神奇。

糟糕的是错误地使用外键,例如反向创建外键。

Juan Manuel 的例子就是一个典型的例子,如果您使用代码,那么在数据库中留下伪造的 DocumentItems 的可能性会更大,而这些伪造的 DocumentItems 会来找您的麻烦。

级联更新非常有用,例如,当您对数据的引用可能发生变化时,比如用户表的主键是名称、姓氏组合。然后,您希望将该组合中的更改传播到引用它们的任何地方。

@ Aidan 你提到的清晰度代价很高,有可能在数据库中留下伪造的数据,也就是 不小。对我来说,这通常只是缺乏对数据库的熟悉和无法找到哪些 FK 是在与数据库工作之前,培养恐惧。要么是这样,要么就是不断地滥用级联,在实体概念上没有关联的地方使用级联,或者在必须保存历史的地方使用级联。

我做了大量的数据库工作,很少发现级联删除有用。我唯一一次有效地使用它们是在一个报表数据库中,该数据库通过夜间作业进行更新。通过删除自上次导入以来已经更改的任何顶级记录,然后重新导入已修改的记录以及与它们相关的任何内容,我确保正确导入了所有已更改的数据。它使我不必编写大量复杂的删除操作,从数据库的底部到顶部都可以看到这些操作。

我不认为级联删除和触发器一样糟糕,因为它们只是删除数据,触发器里面可能有各种各样讨厌的东西。

一般来说,我完全避免真正的删除,而是使用逻辑删除(即。取而代之的是一个名为 isDelete 的位列,该列被设置为 true)。

目前为止我所看到的总结如下:

  • 有些人根本不喜欢瀑布。

级联删除

  • 当关系的语义可能包含一个独占的 描述时,级联删除可能是有意义的。例如,OrderLine 记录是其父顺序的一部分,并且 OrderLines 永远不会在多个订单之间共享。如果 Order 消失了,OrderLine 也应该消失,没有 Order 的行将是一个问题。
  • Cascade Delete 的典型例子是 Some Object 和 Some ObjectItems,其中没有相应的主记录,条目记录的存在就没有任何意义。
  • 如果要保留历史或使用“软/逻辑删除”(只将删除的位列设置为1/true) ,则应使用 没有级联删除。

级联更新

  • 当跨表使用实际键而不是代理键(标识/自动增量列)时,级联更新可能有意义。
  • Cascade Update 的规范示例是当您有一个可变的外键(如可更改的用户名)时。
  • 没有应该使用级联更新(Cascade Update)和 Identity/auto 自動增量列。
  • 级联更新最好与唯一约束结合使用。

何时使用级联

  • 在允许级联操作之前,您可能希望从用户那里获得额外的强确认,但这取决于您的应用程序。
  • 如果错误设置外键,级联可能会给您带来麻烦。但如果你做得对,你应该会没事的。
  • 在完全理解之前使用级联是不明智的。然而,它是一个有用的特性,因此值得花时间去理解。

和这里的其他人一样,我发现级联删除真的只有很小的帮助(删除其他表中的引用数据真的没有那么大的工作量——如果有很多表,你只需要用一个脚本来自动完成) ,但是当有人意外地级联删除一些难以恢复的重要数据时,真的很烦人。

我使用的唯一情况是,如果表中的数据受到高度控制(例如,有限的权限) ,并且只能通过经过验证的受控进程(如软件更新)进行更新或删除。

我经常处理级联删除。

知道不管是谁在数据库上工作,都不会留下任何不想要的数据,这种感觉很好。如果依赖性增长,我只需在 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 的删除或更新可以通过以下三种方式之一处理:

  1. 拒绝
  2. 繁殖
  3. 无效。

传播被称为级联。

有两种情况:

如果 S 中的元组被删除,则删除引用它的 R 元组。

Something 如果 S 中的元组被更新,则更新引用它的 R 元组中的值。

在物理数据库中实现逻辑超类型和子类型实体时,级联删除非常有用。

当单独的超类型和子类型表被用于物理实现超类型/子类型时(与将所有子类型属性卷入单个物理超类型表相反) ,这些表之间存在一对一的关系,然后问题就变成如何保持这些表之间的主键100% 同步。

级联删除可以成为一个非常有用的工具:

1)确保删除超类型记录也会删除相应的单个子类型记录。

2)确保对子类型记录的任何删除也会删除超类型记录。这是通过在子类型表上实现一个“代替”删除触发器来实现的,该触发器会删除相应的超类型记录,而超类型记录又会级联删除子类型记录。

以这种方式使用级联删除确保不存在孤立的超类型或子类型记录,无论您是先删除超类型记录还是先删除子类型记录。

我会区分

  • 数据完整性
  • 业务逻辑/规则

根据我的经验,最好在数据库中尽可能使用 PK、 FK 和其他约束来实现完整性。

然而,业务规则/逻辑 IMO 最好使用代码实现,这是出于内聚的原因(谷歌“耦合和内聚”学习更多)。

级联删除/更新数据完整性还是业务规则?这当然可以辩论,但我要说,这通常是一个逻辑/规则。例如,一个业务规则可能是,如果一个 Order被删除,所有的 OrderItems都应该被自动删除。但也可能是,它应该永远不可能删除一个 Order,如果它仍然有 OrderItems。因此,这可能取决于企业的决定。我们如何知道这条规则当前是如何实现的?如果都在代码中,我们可以只看代码(高内聚力)。如果规则可能在代码中实现,或者可能在数据库中作为级联实现,那么我们需要在多个地方(低内聚)进行查找。

当然,如果您全力以赴只将业务规则放在数据库中并使用触发器,那么级联存储过程可能是有意义的。

在使用任何存储的过程或触发器之前,我通常会考虑数据库供应商锁定。只存储数据并加强完整性的 SQL 数据库更容易移植到其他供应商。因此,我通常不使用存储的进程或触发器。