依赖外键级联是不是很糟糕?

时间:2010-02-05 03:10:46

标签: sql mysql database-design cascade

我参与的项目的首席开发人员表示依靠级联删除相关行是不好的做法。

我不知道这有多糟糕,但我想知道你对是否/原因的想法。

7 个答案:

答案 0 :(得分:18)

我将在前言中说我很少删除行期间。通常,您希望保留大多数数据。您只需将其标记为已删除,这样就不会向用户显示(即向用户显示已删除)。当然这取决于数据和某些东西(例如购物车内容)实际上当用户清空他或她的购物车时删除记录。

我只能假设这里的问题是您可能无意中删除了您实际上不想删除的记录。但参考完整性应该可以防止这种情况所以除了明确的情况外,我无法真正看到反对这一点的原因。

答案 1 :(得分:11)

我会说你遵循最少惊喜的原则。

级联删除不应导致意外的数据丢失。如果删除需要删除相关记录,并且用户需要知道这些记录将要消失,则不应使用级联删除。相反,应该要求用户明确删除相关记录,或者提供通知。

另一方面,如果表与另一个临时性的表相关,或者包含父实体消失后永远不需要的记录,那么级联删除可能没问题。

那就是说,我更喜欢通过删除代码中的相关记录而不是依赖于级联删除来明确说明我的意图。事实上,我从未真正使用级联删除来隐式删除相关记录。另外,我使用了很多软删除,如cletus所述。

答案 2 :(得分:7)

我从不使用级联删除。为什么?因为犯错很容易。要求客户端应用程序 显式 删除(并满足删除条件,例如删除FK引用记录)要安全得多。

事实上,通过将记录标记为已删除或移入存档/历史记录表,可以避免删除本身。

如果将记录标记为已删除,则取决于标记为已删除数据的相对比例,因为SELECT必须过滤“isDeleted = false”,否则只会使用索引记录中少于10%(大约取决于RDBMS)被标记为已删除。

您更喜欢以下两种情况中的哪一种:

1)开发人员来找你,说“嘿,这个删除行不通”。你们都调查它并发现他无意中试图删除整个表格内容。你们都笑了,然后回到你们正在做的事情上。

2)开发人员来找你,羞怯地问“我们有备份吗?”

答案 3 :(得分:3)

避免级联删除的另一个重要原因是性能。在您需要从主表中删除10,000条记录之前,这似乎是一个好主意,而主表又在子表中有数百万条记录。鉴于此删除的大小,它可能会完全锁定所有表几个小时甚至几天。你为什么要冒这个风险?为了方便花费十分钟时间为一条记录删除额外的删除语句?

此外,当您尝试删除具有子记录的记录时获得的错误通常是一件好事。它告诉您,您不想删除此记录,因为如果您这样做,您需要的数据会丢失。如果您删除了过去订单的客户,则级联删除将继续删除子记录,导致丢失有关订单的信息。这种事情可以彻底弄乱你的财务记录。

答案 4 :(得分:1)

我同样被告知,级联删除是不好的做法......因此在我遇到使用它们的客户之前从未使用它们。我真的不知道为什么我不应该使用它们,但认为它们非常方便,不必编码删除所有FK记录。

因此,我决定研究为什么他们如此“糟糕”,而且从我迄今为止发现的情况来看,他们似乎没有任何问题。事实上,到目前为止,我看到的唯一好的论点是HLGLEM上面提到的关于绩效的内容。但由于我通常不会删除这些记录,我认为在大多数情况下使用它们应该没问题。我想听听其他人可能反对使用它们以确保我已经考虑过所有选项的任何其他论点。

答案 5 :(得分:0)

我还要补充一点,即DELETE CASCADE使得使用binlog复制很难维护数据仓库中数据的副本,这是大多数商业ETL工具的工作方式。从每个表中显式删除将保留完整的日志记录,并且在数据团队中更容易:)

答案 6 :(得分:0)

我实际上同意这里的大多数答案,然而并非所有情况都相同,这取决于手头的情况以及该决定的熵是多少,例如:< /p>

如果您有一个删除命令,该实体与大量实体有多个多/属关系,则每次调用该删除过程时,您还需要记住从每个关系主元中删除所有相应的 FK与 A 有对应关系。

而通过级联删除,您将其写入一次作为架构的一部分,它将删除那些相应的 FK 并清除不再需要的关系中的枢轴,想象一下 24 个关系一个实体 + 其他实体,在此之上也有大量关系,同样,这真的取决于您的设置以及您对什么感到满意。无论如何,仅供参考,在 Illuminate 迁移模式文件中,您可以这样编写:

            $table->dropForeign(['permission_id']);

            $table->foreign('permission_id')
                ->references('id')
                ->on('permission')
                ->onDelete('cascade');