外键有什么问题?

我记得听说Joel Spolsky在播客014中提到他几乎没有使用过外键(如果我没记错的话)。 不过,对我来说,避免整个数据库中的重复和后续数据完整性问题似乎非常重要。

人们为什么(为了避免与Stack Overflow原则一致的讨论)有一些坚实的理由?

编辑: “我还没有理由创build一个外键,所以这可能是我第一个真正build立一个外键的原因。

使用外键的原因:

  • 你不会得到孤行
  • 你可以得到很好的“删除级联”的行为,自动清理表
  • 了解数据库中表之间的关系有助于Optimizer计划您的查询以获得最有效的执行,因为它能够更好地估计join基数。
  • FK提供了一个相当大的暗示,哪些统计数据最重要收集在数据库上,从而导致更好的性能
  • 他们使各种自动生成的支持 – ORMs可以自己生成,可视化工具将能够为您创build好的架构布局等
  • 一个新来的项目会更快地进入事物的stream程,因为否则隐式的关系会被明确地logging下来

不使用外键的原因:

  • 您正在使每个CRUD操作的DB额外工作,因为它必须检查FK一致性。 这可能是一个很大的成本,如果你有很多stream失
  • 通过执行关系,FK指定一个顺序,你必须添加/删除的东西,这可能会导致数据库拒绝做你想做的事情。 (当然,在这种情况下,你要做的是创build一个孤行,这通常不是一件好事)。 当你进行大量的批量更新时,这是特别痛苦的,并且在另一个表之前加载一个表格,第二个表格创build一致的状态(但是如果有可能第二次加载失败,数据库现在不一致?)。
  • 有时候你事先知道你的数据将会变得很脏,你接受这个,而且你希望数据库接受它
  • 你只是懒惰:-)

我认为(我不确定!)大多数已build立的数据库提供了一种方法来指定一个没有强制执行的外键,而且只是一些元数据。 由于不执行任何不使用FK的理由,如果第二部分中的任何原因适用,你应该走这条路。

这是一个养育的问题。 如果在你的教育或职业生涯中有一段时间你花时间喂养和关心数据库(或者与有才干的人密切合作),那么实体和关系的基本原则在你的思维过程中是根深蒂固的。 在这些基础之中是如何/何时/为什么要在数据库中指定键(主键,外键和可能的备用键)。 这是第二天性。

但是,如果您在过去与RDBMS相关的工作中没有这么全面或积极的经验,那么您可能没有接触到这些信息。 或者,也许你的过去包括沉浸在一个大声反数据库的环境中(例如,“那些数据库pipe理员是白痴 – 我们很less,我们select了几个java / c#代码sl手将节省一天的时间”),在这种情况下,你可能会强烈反对对某些dweeb的神秘bab语,告诉你如果你只是听,FK(以及它们可以暗示的限制)真的很重要。

当他们是孩子时,大多数人都被教导,刷牙是非常重要的。 没有它你能得到吗? 当然,但是在某个地方,如果每顿饭之后刷牙的话,你的牙齿可用量就会减less。 如果父母对数据库devise和口腔卫生有足够的责任,我们就不会有这种对话。 🙂

引用Joe Celko的话,

“就像26号的丁字裤,只是因为你可以不意味着你应该!”

我敢肯定,有很多应用程序可以逃脱它,但它不是最好的主意。 你不能总是依靠你的应用程序来正确地pipe理你的数据库,坦率地说,pipe理数据库不应该是你的应用程序非常担心的。

如果您正在使用关系数据库,那么您应该在其中定义一些关系 。 不幸的是,这种态度(你不需要外键)似乎被许多应用程序开发人员所接受,他们宁愿不被诸如数据完整性(但是因为他们的公司没有专门的数据库开发人员)等愚蠢的东西所困扰。 通常在这些types的数据库中,你只是有主键是幸运的;)

外键对于任何关系数据库模型都是必不可less的

我总是使用它们,但是我为金融系统制作数据库。 数据库是应用程序的关键部分。 如果金融数据库中的数据不完全准确,那么将代码/前端devise付出多less努力确实无关紧要。 你只是在浪费你的时间。

还有一个事实,即多个系统通常需要直接与数据库接口 – 从其他只读取数据的系统(Crystal Reports)到插入数据的系统(不一定使用我devise的API;它可能由刚刚发现VBScript并具有SQL框的SA密码的枯燥经理)。 如果数据库不像它可能的那样是白痴的certificate,那再见了再见数据库。

如果你的数据很重要,那么是的,使用外键,创build一套存储过程来与数据进行交互,然后制作最棘手的数据库。 如果你的数据不重要,你为什么要创build一个数据库?

外键使自动化testing变得复杂

假设你正在使用外键。 你正在写一个自动化testing,说:“当我更新一个财务账户时,它应该保存一个交易logging。” 在这个testing中,你只关心两个表: accountstransactions

然而, accountscontracts的外键, contractsclients有fk, clients有fk到citiescities有fk到states

现在,数据库将不允许您在没有与您的testing相关的四个表中设置数据的情况下运行testing

至less有两个可能的观点:

  • “这是一件好事:你的testing应该是现实的,这些数据的限制将存在于生产中。”
  • “这是一件坏事:你应该能够单独testing系统的各个部分而不涉及其他部分,你可以为整个系统添加集成testing。”

运行testing时暂时closures外键检查也是可能的。 MySQL至less支持这一点 。


更新 :我现在总是使用外键。 我的答案是“他们复杂的testing”是“编写你的unit testing,所以他们根本不需要数据库,任何使用数据库的testing都应该正确地使用它,包括外键,如果设置是痛苦的,find一个不那么痛苦的方式来做这个设置。“

@imphasing – 这正是那种导致维护噩梦的心态。

为什么哦,为什么你会忽视声明性的参照完整性,在这种情况下,数据可以保证至less一致,有利于所谓的“软件执行”,这是一个最好的弱预防措施。

没有很好的理由使用它们,除非孤立的行对我来说不算什么大事。

“他们可以使删除logging更加繁琐 – 你不能删除”主“logging,如果其他表中有外键违反该限制的logging。

请记住,SQL标准定义了外键被删除或更新时所采取的操作。 我所知道的是:

  • ON DELETE RESTRICT – 防止其他表中具有此列中的键的任何行被删除。 这是Ken Ray上面所描述的。
  • ON DELETE CASCADE – 如果其他表中的行被删除,则删除此表中引用它的任何行。
  • ON DELETE SET DEFAULT – 如果删除另一个表中的行,则将引用它的外键设置为该列的默认值。
  • ON DELETE SET NULL – 如果其他表中的行被删除,则将在此表中引用它的外键设置为null。
  • ON DELETE NO ACTION – 这个外键只标记它是一个外键; 即用于OR映射器。

这些相同的操作也适用于ON UPDATE。

默认情况下,似乎取决于您正在使用的SQL服务器。

有一个很好的理由不使用它们: 如果你不明白他们的angular色或如何使用它们。

在错误的情况下,外键约束会导致事故的瀑布复制。 如果有人删除了错误的logging,撤消它可能成为一项艰巨的任务。

此外,相反,当你需要删除的东西,如果devise不佳,约束可能会导致各种各样的锁,阻止你。

更大的问题是:你会蒙着眼睛开车吗? 这就是如果你开发一个没有参考约束的系统。 请记住,业务需求改变,应用程序devise更改,代码更改中各自的逻辑假设,逻辑本身可以被重构等等。 总的来说,数据库中的约束条件是在当代的逻辑假设条件下进行的,似乎对特定的逻辑断言和假设是正确的。

通过应用程序的生命周期,引用和数据检查通过应用程序限制警察数据收集,特别是当新的需求推动逻辑应用程序更改时。

对于这个清单的主题,从实时事务处理系统的angular度来看,外键本身并不能“提高性能”,也不会“显着降低性能”。 但是,在批量“批量”系统中,有一个用于约束检查的总成本。 所以,这里是区别,实时与批量交易过程; 批处理 – 通过约束检查获得的累计成本,按顺序处理的批处理会造成性能下降。

在一个devise良好的系统中,数据一致性检查将在“处理批量”之前完成(不过,这里也有相关的成本); 因此,在加载期间不需要外键约束检查。 实际上,包括外键在内的所有约束都应暂时禁用,直到批处理完成。

QUERY PERFORMANCE – 如果表是通过外键连接的,则应该认识到外键列是NOT INDEXED(尽pipe相应的主键是按照定义索引的)。 通过对外键进行索引,就可以通过索引任何键,并且在索引帮助下连接表来获得更好的性能,而不是通过连接带有外键约束的非索引键。

如果数据库只是支持网站显示/渲染内容/等等,并logging点击,那么对所有表格完全约束的数据库就会因为这样的目的而被杀死。 想想看。 大多数网站甚至不使用这样的数据库。 对于类似的需求,数据只是被logging下来而没有被引用,使用一个没有约束的内存数据库。 这并不意味着没有数据模型,是逻辑模型,但没有物理数据模型。

使用外键的其他原因: – 允许更好地重用数据库

不使用外键的其他原因: – 您试图通过减less重复使用将客户locking到工具中。

我同意以前的答案,因为它们有助于保持数据的一致性。 然而,几周前Jeff Atwood发表了一篇有趣的文章 ,讨论了规范化和一致性数据的优缺点。

简而言之,处理大量数据时,非规范化数据库可能会更快; 你可能不关心应用程序的精确一致性,但是它在处理数据时要求你更小心,因为数据库不会。

我只知道Oracle数据库,没有其他的,我可以告诉你外键是维护数据完整性的关键。 在插入数据之前,需要build立一个数据结构,并使之正确。 完成后 – 即创build所有主键和外键 – 工作完成!

含义:孤行? 不,从来没有见过我的生活。 除非一个糟糕的程序员忘记了外键,或者如果他在另一个层面上实现这个外键。 在Oracle的背景下,这两个都是巨大的错误,这将导致数据重复,孤立数据,从而导致数据损坏。 我无法想象没有FK的数据库强制执行。 它看起来像混乱给我。 这有点像Unix权限系统:想象每个人都是根。 想想混乱。

外键是必不可less的,就像主键一样。 这就像说:如果我们删除主键? 那么总的混乱将会发生。 那是什么 您不能将主要或外部关键责任移到编程级别,它必须处于数据级别。

缺点 ? 是的,一点没错 ! 因为插入,更多的检查将会发生。 但是,如果数据完整性比性能更重要,那么这是一件不容易的事情。 Oracle上的性能问题更多地与PK和FK的索引有关。

他们可以使删除logging更麻烦 – 你不能删除“主”logging,其他表中有外键违反约束的其他表中的logging。 您可以使用触发器进行级联删除。

如果你不明智地select了主键,那么改变这个值就变得更加复杂。 例如,如果我把我的“客户”表的PK作为人的名字,并在“订单”表中将该键设为FK,如果客户想要更改他的名字,那么这是一个皇家的痛苦。但是那只是以次充好的数据库devise。

我相信使用火柴钥匙的优点超过了任何假设的缺点。

validation外键约束需要一些CPU时间,所以有些人省略了外键来获得一些额外的性能。

Clarify数据库是没有主键或外键的商业数据库的例子。

http://www.geekinterview.com/question_details/18869

有趣的是,技术文档很长时间来解释表是如何相关的,哪些列用于join等等。

换句话说,他们可以用明确的声明(DRI)join表格,但是他们没有select

因此,澄清数据库充满了不一致性,并且performance不佳。

但是,我认为这使得开发人员的工作更容易,不必编写代码来处理参照完整性,如在删除,添加之前检查相关行。

而且,我认为,在关系数据库中没有外键约束是主要的好处。 这使得它更容易发展,至less从恶魔可能护理的angular度来看。

我也听到了这个说法 – 那些忘记把外键索引的人,然后抱怨某些操作很慢(因为约束检查可以利用任何索引)。 所以总结一下:没有理由不使用外键。 所有现代数据库支持级联删除,所以…

我所听到的论点是,前端应该有这些商业规则。 当你不应该允许任何插入,首先打破你的约束,外键“添加不必要的开销”。 我同意吗? 不,但这是我一直听到的。

编辑:我的猜测是他指的是外键约束 ,而不是外键作为一个概念。

对我来说,如果你想按照ACID的标准行事 ,那么使用外键来保证引用的完整性是至关重要的。

我必须在这里再次提到大部分意见,外键是保证数据完整性的必要条件。 ON DELETE和ON UPDATE的不同选项将允许您避开人们在此提及的有关其使用的“下降”。

我发现,在99%的项目中,我都会使用FK来强化数据的完整性,然而,在我有客户必须保留其旧数据的罕见情况下,不pipe这些数据有多糟糕。但是后来我花了很多时间写代码,无论如何只能得到有效的数据,所以变得毫无意义。

在整个应用程序生命周期中,可维护性和持久性如何? 大多数数据比使用它的应用程序具有更长的使用寿命。 关系和数据完整性太重要了,以至于希望下一个开发团队能够在应用程序代码中正确使用它。 如果你没有使用不尊重自然关系的脏数据的数据库,你会的。 数据完整性的重要性将变得非常清晰。

我也认为外键在大多数数据库中是必需的。 唯一的缺点(除了执行一致性带来的性能打击之外)是拥有一个外键允许人们编写假定有一个function性外键的代码。 这绝不应该被允许。

例如,我见过有人写代码插入到被引用的表中,然后尝试插入到引用表中,而不validation第一次插入是否成功。 如果稍后删除外键,则会导致数据库不一致。

您也没有在更新或删除时采取特定行为的选项。 无论是否存在外键,您仍然需要编写代码来执行所需操作。 如果您认为删除是级联的,则删除操作将失败。 如果您假设对引用列的更新传播到引用行,则更新将失败。 为了编写代码的目的,你可能不会有这些function。

如果这些function被打开,那么你的代码会模仿它们,你会失去一点点的性能。

所以,总结….如果你需要一个一致的数据库,外键是必不可less的。 永远不应该假定外键在您编写的代码中存在或有效。

我赞同德米特里的回答 – 非常好。

对于那些担心FK经常带来的性能开销的人来说,有一种方法(在Oracle中),您可以在插入,删除或更新期间获得FK约束的查询优化器优势,而不会产生约束validation的成本开销。 那就是用属性RELY DISABLE NOVALIDATE创buildFK约束。 这意味着查询优化器假定在构build查询时约束已经被执行,而数据库没有实际执行约束。 你必须非常小心地承担这个责任,当你用这样的FK约束填充表格时,要确保你的FK列中没有违反约束条件的数据,就好像你这样做了可能会得到不可靠的结果,涉及到这个FK约束的表的查询。

我通常在我的数据集市模式中的某些表上使用这种策略,但不是在我的集成登台架构中。 我确保我从中复制数据的表已经具有相同的约束,或者ETL例程强制约束。

许多在这里回答的人都太过于强调通过参照约束来实施参照完整性的重要性。 在参照完整性的情况下处理大型数据库只是performance不佳。 Oracle似乎在级联删除方面尤其糟糕。 我的经验法则是应用程序不应该直接更新数据库,而应该通过存储过程。 这使代码库保持在数据库中,并且意味着数据库保持其完整性。

在许多应用程序可能访问数据库的情况下,由于参照完整性约束而出现问题,但是这只是一个控制。

还有一个更广泛的问题,那就是应用程序开发人员可能有非常不同的要求,数据库开发人员可能不一定熟悉。

如果你绝对确信,那么一个底层的数据库系统将来不会改变,我会使用外键来保证数据的完整性。

但是这里有另外一个非常好的现实理由,根本不使用外键:

你正在开发一个产品,它应该支持不同的数据库系统。

如果您正在使用能够连接到许多不同数据库系统的entity framework,则可能还需要支持“免费开放源代码”无服务器数据库。 不是所有的这些数据库都可以支持你的外键规则(更新,删除行…)。

这可能会导致不同的问题:

1.)当数据库结构被创build或更新时,您可能会遇到错误。 也许只会有沉默的错误,因为你的外键只是被数据库系统忽略。

2.)如果你依赖外键,你会在你的业务逻辑中做出更less甚至更less的数据完整性检查。 现在,如果新的数据库系统不支持这些外键规则,或者只是以不同的方式运行,则必须重写业务逻辑。

你可能会问:谁需要不同的数据库系统? 那么,并不是每个人都可以负担得起,或者想要在他的机器上完整的SQL Server。 这是需要维护的软件。 其他人已经在其他一些数据库系统上投入了时间和金钱。 无服务器数据库仅适用于一台机器上的小型客户。

没有人知道,所有这些数据库系统如何运作,但是具有完整性检查的业务逻辑始终保持不变。

我一直认为这是懒惰的不使用它们。 我被教导应该总是这样做。 但是,我没有听Joel的讨论。 他可能有一个很好的理由,我不知道。

有一次FK可能会导致您的问题是当您有历史数据引用键(在查找表),即使您不再需要密钥可用。
显然,解决scheme是在事前devise更好的东西,但是我正在考虑现实世界的情况,在这种情况下,您并不总是能够控制整个解决scheme。
例如:也许你有一个查找表customer_type列出不同types的客户 – 可以说你需要删除某个客户types,但(由于业务限制)无法更新客户端软件,也没有人想到这个在开发软件的情况下,即使您知道引用它的历史数据是不相关的,但是在其他表中的外键可能会阻止您删除该行。
经过这几次被烧,你可能会从数据库执法的关系。
(我不是说这是好的 – 只是给出一个原因,为什么你可能会决定避免FK和db的一般限制)

我会回应德米特里所说的,但加上一点。

我在一个批量计费系统上工作,需要在30多个表上插入大量的行。 我们不允许做数据泵(Oracle),所以我们不得不进行批量插入。 那些表有外键,但我们已经确保他们没有打破任何关系。

在插入之前,我们禁用外键约束,这样Oracle就不会永远做插入操作。 插入成功后,我们重新启用约束。

PS:在一个logging有很多外键和子行数据的大型数据库中,有时候外键可能不好,你可能想要禁止级联删除。 对于计费系统中的我们来说,如果我们进行级联删除的话,会花费太多的时间来对数据库征税,所以我们只需要在主驱动程序(父级)表中将该logging标记为不合格。

像许多事情一样,这是一个折衷。 这是一个问题,你要做的工作,以validation数据的完整性:

(1)使用一个外键(单点configuration一个表,function已经实现,testing,certificate是可行的)

(2)将其留给数据库的用户(可能多个用户/应用程序更新相同的表,这意味着更多潜在的故障点并增加testing的复杂性)。

(2)对数据库更有效,(1)更容易维护,风险更小。