何时/为什么在SQL Server中使用级联?


149

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

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

我正在寻找每种情况的具体示例,最好是从成功使用它们的人那里获得。


4
这个问题似乎与SQL Server并不严格相关,而看起来更像是一个理论上的通用问题。如果删除sql-server标签,对社区会更有用。
clapas

4
@clapas老实说,如果今天我要问这个问题,那将是不合时宜的。如果不是因为高意见/投票表明它对社区有价值,我只是将其删除。
Joel Coehoorn

6
@JoelCoehoorn-显然,这类问题很有价值。由于时间的流逝,该值不会消失。我心中的问题是,我们今天拒绝这样的问题会失去多少价值?
P.Brian.Mackey

2
@ P.Brian.Mackey在这里,在这里!我见过的一些最好的问题/答案是那些被否决或被描绘成题外话的问题...但是您可以通过大量的赞成票告诉我们,许多人都有完全相同的问题!
安东尼·格里格斯

级联操作采用可序列化的锁。
米奇·

Answers:


126

到目前为止我所看到的摘要:

  • 有些人根本不喜欢级联。

级联删除

  • 当关系的语义可能涉及排他性的“是 ”描述的一部分时,级联删除可能很有意义。例如,OrderLine记录是其父订单的一部分,并且OrderLines永远不会在多个订单之间共享。如果Order消失,OrderLine也应该消失,没有Order的行将成为问题。
  • 级联删除的典型示例是SomeObject和SomeObjectItems,在没有相应的主记录的情况下,项目记录永远不会存在。
  • 你应该使用级联删除,如果你保存历史或使用“软/逻辑删除”,您只设置一个删除位列1 /真。

级联更新

  • 当您在表中使用实键而不是替代键(标识/自动增量列)时,级联更新可能会很有意义。
  • 当您具有可变的外键(例如可以更改的用户名)时,Cascade Update的典型示例就是。
  • 你应该使用级联更新与被标识/自动增量列键。
  • 级联更新最好与唯一约束结合使用。

何时使用级联

  • 您可能希望在允许操作进行级联之前从用户那里得到额外的确认,但这取决于您的应用程序。
  • 如果您错误地设置了外键,级联会给您带来麻烦。但是,如果您做对的话,您应该可以。
  • 在完全理解层叠之前,使用层叠是不明智的。但是,它是一个有用的功能,因此值得花一些时间来理解。

3
注意,在“所谓的”自然键似乎不是这些真正有效的唯一键的地方,也经常使用级联更新。实际上,我坚信只有规范化欠佳的数据库模型才需要级联更新,并且它们为混乱的表和混乱的代码打开了大门。
菲利普·格隆迪尔

1
您缺少一个要点,如果有很多子记录,级联会产生巨大的性能问题。
HLGEM

13
@HLGEM-我看不到相关性。如果级联操作导致速度变慢,则等效的手动过程将导致相同的速度变慢,或者在事务需要回滚的情况下没有得到正确的保护。
Joel Coehoorn

3
为什么在IDENTITY或自动增量列上进行级联更新会很重要?我可以看到,为什么它不会是必要的,因为你不应该需要改变的(任意)值,但如果其中一人做了改变,至少引用完整性是完好的。
肯尼·埃维特

3
10个子弹?好了,现在我们知道乔尔没有开枪。
尼尔N

68

外键是确保数据库引用完整性的最佳方法。避免由于魔术而导致的级联就像在汇编中编写所有内容一样,因为您不相信编译器背后的魔术。

不好的是外键的错误使用,例如,向后创建外键。

胡安·曼努埃尔(Juan Manuel)的例子就是规范的例子,如果您使用代码,那么有更多的机会在数据库中留下伪造的DocumentItems来咬您。

级联更新非常有用,例如,当您通过可以更改的内容引用数据时,例如,用户表的主键是名称,姓氏组合。然后,您希望该组合中的更改传播到引用的任何位置。

@Aidan,您所指的这种清晰度付出了高昂的代价,这是将假数据留在数据库中的机会,这是不小的机会。对我来说,通常只是缺乏对DB的熟悉,并且在与DB合作之前无法找到哪些FK会加剧这种恐惧。要么,要么是不断地滥用级联,在实体在概念上不相关的地方,或者必须保留历史的地方使用级联。


7
首先,使用这种“自然”主键是一个非常糟糕的主意。
尼克·约翰逊,

4
RE:评论针对Aidan。不,在FK上保留CASCADE不会增加保留虚假数据的机会。它减少了命令会影响到比预期更多的数据的机会,并增加了代码。完全省略FK会留下伪数据的机会。
香农遣散费

5
在我的职业生涯中至少有两次看到被误解的级联删除对业务造成威胁的后果,除了最明确的案例外,我非常不愿意在所有情况下使用它们。在这两种情况下,由于级联的结果而删除了数据,而级联实际上应该保留但不是-直到正常的备份周期失去了轻松还原的可能性后,才检测到丢失的数据。从纯粹的逻辑观点来看,Vinko是正确的,但是在现实世界中,使用级联会使人为失误和意外后果暴露得比我想要的更多。
Cruachan

1
我实际上已经对系统进行了编码,在这些系统中,已经做出了管理决策,即用户必须删除母版明细中的所有子级,然后才能删除母版,以迫使用户进行思考。在逻辑上可能的情况下,不使用级联是一种类似的防火措施。
Cruachan 2010年

6
@Cruachan:我认为规则很简单。如果数据与没有父数据的数据没有那么紧密的联系,那么它就不保证级联关系。这就是我在答案的最后一句话中试图解决的问题。
Vinko Vrsalovic

17

我从不使用级联删除。

如果我想从数据库中删除某些内容,我想明确地告诉数据库我要取出的内容。

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

我喜欢从代码(或存储过程)中完成代码而获得的清晰度,而不是发生“魔术”事件。

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

需要注意的是,即使您删除了一个“订单”,即使级联删除删除了50个“ orderItem”,您也将得到“ 1行受影响”的报告。


34
为什么还不摆脱主键呢?您将清楚确保代码中的唯一值。
MusiGenesis

4
@ MusiGenesis,Aidan不主张删除FK。FK仍会保护数据,但是如果没有CASCADE ON ....不会发生意外的魔术。
香农遣散费

7
@Vinko:删除和更新具有定义良好的默认语义。通过级联或触发器更改行为以执行更多工作,有可能完成的工作比预期的多。不,我没有测试就工作,是的,我的数据库已记录在案。但是我在编写代码时还记得所有文档吗?如果我想使用更高级别的语义(例如删除父级和子级),而不是编写并使用SP来做到这一点。
香农遣散费

3
@Vinko。魔术的问题不在于合格的开发人员或DBA的问题,而是5年后的Joe interen,他在DBA休假时被赋予了“简单”的维护任务,然后他们在没有人意识到的情况下搞砸了公司数据。级联有自己的位置,但是在部署级联之前,必须考虑整个环境,包括人为因素。
Cruachan 2010年

5
@Vinko:为什么选择“ Gasp” SP?SP无疑是数据库成为企业关键资产的一种方式。在讨论限制将所有数据访问限制到SP或除Select之外的所有访问方面,有一种强烈的论点。请在stackoverflow.com/questions/1171769/…
Cruachan 2010年

13

我经常进行级联删除。

知道有人对数据库进行操作可能永远都不会留下任何不需要的数据,这真是一件好事。如果依赖性增加,我只需在Management Studio中更改图表中的约束即可,而不必调整sp或dataacces。

就是说,关于级联删除和循环引用,我有1个问题。这通常会导致数据库中没有级联删除的部分。


1
我知道这已经很老了,但是+1代表CASCADE DELETE的循环引用问题。
代码小牛

2
请原谅一个菜鸟问题:如果您得到一份循环参考,实际上会发生什么?
蒂姆·洛弗尔-史密斯,

10

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

我不认为级联删除像触发器一样糟糕,因为它们仅删除数据,触发器内部可能包含各种讨厌的东西。

通常,我完全避免使用真正的Delete方法,而是使用逻辑删除方法(例如,将名为isDeleted的位列设置为true)。


2
您让我好奇地学习更多。为什么您强烈喜欢逻辑删除?您正在使用的数据与它有关系吗?
蒂姆·洛弗尔-史密斯,

9

一个示例是当您在实体之间具有依赖关系时,即:Document-> DocumentItems(删除Document时,DocumentItems没有理由存在)


5

如果希望删除带有FK的记录,请使用级联删除。换句话说,在没有引用记录的情况下记录是没有意义的。

我发现级联删除对确保默认情况下删除无效引用而不是导致空异常有用。


5

开删除级联:

想要删除子表中的行时如果在父表中删除相应的行

如果不使用级联删除,则会导致参照完整性的错误。

在更新级联上:

当您想更改主键中的外键中的更新


5

我听说过DBA和/或“公司策略”完全是由于过去的不良经验而禁止使用“ On Delete Cascade”(及其他)。在一个案例中,一个人写了三个触发器,最终互相呼唤。三天的恢复导致完全禁止扳机,这都是由于一个同志的行动。

当然,有时需要触发器而不是“ On Delete Cascade”,例如需要保留一些子数据时。但是在其他情况下,使用“删除时”级联方法完全有效。“删除级联”的主要优点是它可以捕获所有子级。如果编写的自定义触发器/存储过程编码不正确,则可能不会。

我认为应该允许开发人员根据开发内容和规格说明做出决定。基于不良经历的地毯禁令不应作为标准;“从不使用”的思维过程充其量是严酷的。每次都需要做出判断,并且随着业务模型的变化而进行更改。

这不是发展的全部内容吗?


我没想到它会删除所有内容 ……您是说该功能实际上按照它说的去做?...
Joel Coehoorn 2014年

3

进行级联删除(而不是在代码中进行删除)的一个原因是提高性能。

情况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'

5
不知道使用哪个数据库,我建议您手动删除的效果要比级联删除差,因为它不是基于设置的。在大多数数据库中,您可以基于与另一个表的联接进行删除,因此具有基于集合的删除要比遍历记录快得多。
HLGEM

2

我尝试避免删除或更新我未在SQL Server中明确请求的内容。

通过级联或通过使用触发器。在尝试跟踪错误或诊断性能问题时,它们往往会在一段时间内咬住您。

我用它们的目的是为了保证一致性,而无需付出太多努力。为了获得相同的效果,您将必须使用存储过程。


2

我和这里的其他人一样,发现级联删除实际上仅在一定程度上有所帮助(删除其他表中的引用数据确实没有太多工作-如果有很多表,您只需使用脚本即可自动执行此操作),但确实很烦人当某人意外地级联删除一些难以恢复的重要数据时。

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


1

可以通过以下三种方式之一来处理对S的删除或更新,以删除在R的某些元组中找到的外键值:

  1. 拒绝
  2. 传播
  3. 无效。

传播称为级联。

有两种情况:

‣如果删除了S中的元组,请删除引用它的R元组。

‣如果更新了S中的元组,请更新引用它的R元组中的值。


0

如果您正在使用具有不同版本的许多不同模块的系统,则如果级联删除的项目是PK持有者的一部分/拥有的,这将非常有帮助。否则,所有模块在删除PK所有者之前都需要立即打补丁以清理其依赖项,否则,如果未正确执行清理,则将完全省略外键关系,这可能会在系统中留下大量垃圾。

在不鼓励层叠删除一段时间后,我刚刚为两个已经存在的表之间的新交集表(仅要删除的交集)引入了级联删除。如果数据丢失,也还不错。

但是,对于像枚举一样的列表表来说,这是一件坏事:有人从表“ colors”中删除条目13-黄色,并且删除了数据库中的所有黄色项。此外,有时这些内容会以全部删除全部插入的方式进行更新,从而导致完全省略了参照完整性。当然,这是错误的,但是您将如何更改已经运行多年的复杂软件,而引入真正的参照完整性却会带来意料之外的副作用?

另一个问题是,即使删除了主键后仍应保留原始外键值。可以为原始FK创建一个逻辑删除列和一个ON DELETE SET NULL选项,但这又需要触发器或特定的代码来维护冗余(PK删除后除外)键值。


0

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

当使用单独的超类型和子类型表物理地实现超类型/子类型时(与将所有子类型属性汇总到单个物理超类型表中相反),这些表和问题之间的一种关系就变成了如何使这些表之间的主键100%保持同步。

级联删除对于以下操作而言可能是非常有用的工具:

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

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

以这种方式使用级联删除操作可以确保不存在任何孤立的超类型记录或子类型记录,而不管您是先删除超类型记录还是先删除子类型记录。


好的例子。在JPA中,它是InheritanceStrategy Joined表。对于1):通常,您使用的是持久层框架(EclipseLink,Hibernate等),该框架为已连接的实体实现已删除的序列,以首先删除已连接的部分,然后是上级部分。但是,如果您拥有更多的基本软件,例如导入或存档作业,则可以通过在上级部件上执行删除操作来删除实体,这很方便。关于2):同意,但在这种情况下,客户应已经意识到他正在从事实体的加入/子部分。
leo
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.