我记得听到Joel Spolsky在播客014中提到他几乎从未使用过外键(如果我没记错的话)。然而,对我来说,它们对于避免数据库中的重复和后续数据完整性问题非常重要。
人们是否有一些可靠的理由(以避免与Stack Overflow原则一致的讨论)?
编辑: 我还没有理由创建一个外键,所以这可能是我真正建立一个外键的第一个理由。< / em >
没有好理由不使用它们…除非孤行对你来说不是什么大问题。
任何关系数据库模型的外键都是至关重要的。
我一直认为不用它们是懒惰的表现。我被教导应该一直这样做。但后来,我没有听乔尔的讨论。他也许有充分的理由,我不知道。
它们会使删除记录变得更加麻烦——当其他表中存在外键违反约束的记录时,您就不能删除“主”记录。可以使用触发器进行级联删除。
如果不明智地选择主键,则更改该值将变得更加复杂。例如,如果我有我的“客户”表的PK作为人的名字,并使该键在“订单”表中为FK,如果客户想更改他的名字,那么这是一个巨大的痛苦……但这只是粗制滥造的数据库设计。
我相信使用火密钥的优点大于任何假定的缺点。
我听到的观点是前端应该有这些业务规则。当您一开始就不应该允许任何破坏约束的插入时,外键会“增加不必要的开销”。我同意吗?不,但我一直都是这么听说的。
我猜他指的是外键约束,而不是外键作为一个概念。
我可以看到使用外键的一些原因(有人提到,孤立行很烦人),但我也从不使用它们。对于一个相对正常的DB模式,我不认为它们是100%需要的。约束是好的,但我认为通过软件来实施约束是更好的方法。
亚历克斯
我同意前面的答案,因为它们对维护数据一致性很有用。然而,几周前有一个Jeff Atwood的有趣帖子讨论了规范化和一致数据的利弊。
简而言之,在处理大量数据时,非规范化数据库可以更快;你可能不关心精确的一致性取决于应用程序,但它迫使你在处理数据时更加小心,因为DB不会。
有一个很好的理由不使用它们:如果你不了解他们的角色或如何使用他们。
在错误的情况下,外键约束可能导致事故的瀑布式复制。如果有人删除了错误的记录,恢复它可能会成为一项艰巨的任务。
同样,相反,当您需要删除某些内容时,如果设计不当,约束可能会导致各种锁阻止您的操作。
验证外键约束需要一些CPU时间,因此有些人省略外键以获得额外的性能。
澄清数据库是一个没有主键或外键的商业数据库示例。
http://www.geekinterview.com/question_details/18869
有趣的是,技术文档花了很大的篇幅来解释表是如何关联的,用什么列来连接它们等等。
换句话说,它们可以已经用显式声明(DRI)连接了表,但是它们选择不去。
因此,澄清数据库充满了不一致,性能不佳。
但我认为它使开发人员的工作更容易,不必编写代码来处理引用完整性,例如在删除或添加之前检查相关行。
我认为,这就是关系数据库中没有外键约束的主要好处。它使开发变得更容易,至少从不顾一切的角度来看是这样。
@ emphasis——这正是导致维护噩梦的心态。
为什么,哦,为什么你要忽略声明性引用完整性,其中数据可以保证至少是一致的,而支持所谓的“软件强制”,这充其量是一个弱的预防措施。
我相信有很多应用程序可以使用它,但这不是最好的主意。您不能总是指望您的应用程序能够正确地管理数据库,坦白地说,管理数据库不应该是您的应用程序非常关心的问题。
如果您使用的是关系数据库,那么似乎应该在其中定义一些的关系。不幸的是,这种态度(您不需要外键)似乎被许多应用程序开发人员所接受,他们宁愿不为数据完整性等愚蠢的事情所困扰(但需要这样做,因为他们的公司没有专门的数据库开发人员)。通常在这些类型的数据库中,你很幸运只有主键;)
我也听过这种说法——有些人忘记在外键上放索引,然后抱怨某些操作很慢(因为约束检查可以利用任何索引)。所以总结一下:没有好的理由不使用外键。所有现代数据库都支持级联删除,所以…
我经常使用它们,但后来我为金融系统制作数据库。数据库是应用程序的关键部分。如果金融数据库中的数据不是完全准确的,那么无论你在代码/前端设计上投入多少精力都没有意义。你只是在浪费时间。
还有一个事实是,多个系统通常需要直接与数据库接口-从其他系统只是读取数据(Crystal Reports)到系统插入数据(不一定使用我设计的API;它可能是由一个刚刚发现VBScript并拥有SQL框SA密码的愚蠢的经理编写的)。如果数据库不能像白痴一样证明它可能是,好吧,再见数据库。
如果您的数据很重要,那么可以使用外键,创建一套存储过程来与数据交互,并创建最强大的DB。如果您的数据不重要,那么为什么要开始创建数据库呢?
使用外键的原因:
不使用外键的原因:
我认为(我不确定!)大多数已建立的数据库都提供了一种指定外键的方法,这种方法不是强制的,只是一些元数据。由于不强制执行消除了不使用fk的所有理由,如果第二部分中的任何理由适用,您可能应该走那条路。
customer_type
“它们会使删除记录变得更加麻烦——你不能删除“主”记录,而其他表中的记录中有外键会违反这种约束。”
ON DELETE RESTRICT
ON DELETE CASCADE
ON DELETE SET DEFAULT
ON DELETE SET NULL
ON DELETE NO ACTION
这些相同的操作也适用于ON UPDATE。
ON UPDATE
默认值似乎取决于你使用的sql服务器。
对我来说,如果你想要遵循酸标准,拥有外键以确保引用完整性是至关重要的。
我不得不在这里第二多的评论,外键是必要的项目,以确保你有完整的数据。ON DELETE和ON UPDATE的不同选项将允许你绕过一些人们在这里提到的关于它们的使用的“下降”。
我发现在我99%的项目中,我会使用FK来加强数据的完整性,然而,在很少的情况下,我的客户必须保留他们的旧数据,不管它有多糟糕....但后来我花了很多时间写代码,只得到有效的数据,所以它变得毫无意义。
使用外键的其他原因: -允许更大的数据库重用
不使用外键的其他原因: -你试图通过减少重用来锁定客户到你的工具
跨应用程序生命周期的可维护性和稳定性如何?大多数数据的生命周期都比使用它的应用程序长。关系和数据完整性非常重要,不能寄希望于下一个开发团队在应用程序代码中做出正确的处理。如果您没有使用过不尊重自然关系的脏数据的db,那么您将会使用。数据完整性的重要性将变得非常清楚。
我同意德米特里的话,但要补充一点。
我在一个批处理计费系统中工作,需要在30多个表中插入大量的行。我们不允许做数据泵(Oracle),所以我们必须做批量插入。这些表上有外键,但我们已经确保它们不会破坏任何关系。
在插入之前,我们禁用外键约束,这样Oracle就不会一直进行插入。插入成功后,我们重新启用约束。
PS:在一个大型数据库中,一条记录有许多外键和子行数据,有时外键可能不好,您可能希望禁止级联删除。对于在计费系统中的我们来说,如果进行级联删除,将花费太长时间,并且对数据库造成太大负担,因此我们只是在主驱动程序(父)表上使用一个字段将记录标记为坏记录。
我还认为外键在大多数数据库中是必要的。唯一的缺点(除了强制一致性带来的性能损失之外)是使用外键允许人们编写假定存在功能外键的代码。这绝不应该被允许。
例如,我看到有人编写了一些代码,将插入插入引用表中,然后尝试将插入插入引用表中,而不验证第一次插入是否成功。如果稍后删除外键,则会导致数据库不一致。
您也不能在更新或删除时假设特定的行为。无论是否存在外键,您仍然需要编写代码来执行您想要的操作。如果您假设删除是级联的,但实际上它们不是级联的,那么您的删除将失败。如果您假定对引用列的更新被传播到引用行,但实际上没有,那么您的更新将失败。出于编写代码的目的,最好不要使用这些特性。
如果打开了这些特性,那么您的代码无论如何都会模仿它们,并且会损失一些性能。
所以,总结....如果需要一致的数据库,外键是必不可少的。在您编写的代码中,永远不应假定外键存在或起作用。
这是一个教养问题。如果在您的教育或职业生涯中,您曾花时间维护数据库(或与有才华的人密切合作),那么实体和关系的基本原则就会在您的思维过程中根深蒂固。这些基本知识包括如何/何时/为什么在数据库中指定键(主键、外键和备选键)。这是第二天性。
但是,如果您在过去的rdbms相关工作中没有这样彻底的或积极的经验,那么您可能没有接触过这样的信息。或者你的过去可能沉浸在一个大声反对数据库的环境中(例如,“那些dba都是白痴——我们很少,我们选择了几个java/c#代码骗子来拯救世界”),在这种情况下,你可能会强烈反对一些愚蠢的人告诉你,如果你听的话,FKs(以及他们暗示的约束)真的很重要。
大多数人小时候都被教育刷牙很重要。没有它你能过吗?当然,但在某个时候,如果你每顿饭后都刷牙,那么你的牙齿就会减少。如果妈妈和爸爸们有足够的责任心,把数据库设计和口腔卫生都包括在内,我们就不会有这样的对话了。: -)
我同意德米特里的回答,说得很好。
对于那些担心FK经常带来的性能开销的人来说,有一种方法(在Oracle中)可以获得FK约束的查询优化优势,而无需在插入、删除或更新期间验证约束的成本开销。这就是创建带有属性RELY DISABLE NOVALIDATE的FK约束。这意味着查询优化器假定在构建查询时已强制执行约束,而不是数据库实际强制执行约束。当您使用这样的FK约束填充一个表时,您必须非常小心地承担责任,以绝对确保FK列中没有违反约束的数据,因为如果您这样做了,您可能会从涉及这个FK约束所在的表的查询中得到不可靠的结果。
我通常在数据集市模式中的某些表上使用这种策略,但在集成登台模式中不使用。我要确保复制数据的表已经强制执行了相同的约束,或者ETL例程强制执行了该约束。
像许多事情一样,这是一种权衡。这是一个你想在哪里进行验证数据完整性的工作的问题:
(1)使用外键(单点配置为一个表,功能已经实现,经过测试,证明有效)
(2)把它留给数据库的用户(可能多个用户/应用程序更新同一个表),这意味着更多潜在的故障点和测试的复杂性)。
数据库执行(2)更有效,使用(1)更容易维护,风险更小。
更大的问题是:你会戴着眼罩开车吗?如果你开发一个没有参考约束的系统就是这样。请记住,业务需求会改变,应用程序设计会改变,代码中相应的逻辑假设也会改变,逻辑本身也会被重构,等等。一般来说,数据库中的约束是在当代逻辑假设下放置的,对于特定的逻辑断言和假设集似乎是正确的。
在应用程序的整个生命周期中,引用检查和数据检查约束控制通过应用程序收集的数据,特别是当新需求驱动逻辑应用程序更改时。
这张清单的主题——从实时事务处理系统的角度来看,外键本身不会“提高性能”,也不会“降低性能”。然而,在大容量“批处理”系统中,约束检查存在一个聚合代价。这就是实时和批量事务处理的区别;批处理——通过约束检查,按顺序处理的批处理的总成本会对性能造成影响。
在一个设计良好的系统中,数据一致性检查将在处理批处理之前完成(然而,这里也有相关的成本);因此,加载时不需要外键约束检查。事实上,所有的约束,包括外键,都应该暂时禁用,直到批处理完成。
查询性能 -如果表是在外键上连接的,请认识到外键列不是索引的事实(尽管根据定义,各自的主键是索引的)。通过索引一个外键,也就是索引任何键,在索引上连接表有助于提高性能,而不是通过连接一个带外键约束的非索引键。
改变主题,如果一个数据库只是支持网站显示/渲染内容/等,并记录点击,那么一个对所有表都有完全约束的数据库对于这样的目的是多余的。想想看。大多数网站甚至不使用数据库。对于类似的需求,数据只是被记录而不是被引用,使用内存中的数据库,它没有约束。这并不意味着没有数据模型,有逻辑模型,但没有物理数据模型。
数据结构设计的一个好的原则是确保表或对象的每个属性都服从于一个很好理解的约束。这很重要,因为如果您或您的程序可以依赖数据库中的有效数据,那么就不太可能出现由坏数据引起的程序缺陷。您还可以花费更少的时间来编写处理错误条件的代码,并且更有可能预先编写错误处理代码。
在许多情况下,这些约束可以在编译时定义,在这种情况下,您可以编写一个筛选器来确保属性总是在范围内,或者尝试保存属性失败。
然而,在许多情况下,这些约束可以在运行时更改。例如,您可能有一个“cars”表,其属性为“colour”,初始值为“red”、“green”和“blue”。在程序执行期间,可以将有效的颜色添加到初始列表中,并且添加的新“汽车”可以采用最新颜色列表中的任何颜色。此外,您通常希望这个更新的颜色列表在程序重新启动后仍然有效。
为了回答您的问题,事实证明,如果您对数据约束的需求可以在运行时更改,并且这些更改必须在程序重新启动后仍然有效,那么外键是该问题最简单和最简明的解决方案。开发成本是增加一个表(例如。“colors”,“cars”表的外键约束和索引),运行时成本是额外的表查找最新的颜色来验证数据,这个运行时成本通常通过索引和缓存来减轻。
如果您不使用外键来满足这些需求,那么您必须编写软件来管理列表、查找有效的条目、将其保存到磁盘、如果列表很大的话有效地组织数据、确保对列表的任何更新都不会破坏列表文件、在有多个读取器和/或写入器的情况下提供对列表的串行访问,等等。例如,你需要实现大量的RDBMS功能。
在我参与的一个项目中,经常存在隐式关系而不是显式关系,这样可以在同一列上连接多个表。
请看下面的表格
地址
EntityType的值可能是Employee、Company、Customer, EntityId指的是您感兴趣的表的主键。
我真的不认为这是最好的方法,但它对这个项目有效。
更新:我现在总是使用外键。对于反对意见“他们使测试变得复杂”,我的回答是“编写单元测试,这样他们就根本不需要数据库。任何使用该数据库的测试都应该正确地使用它,这包括外键。如果准备工作很痛苦,那就找一种不那么痛苦的方式来做。”
假设您正在使用外键。您正在编写一个自动测试,该测试表示“当我更新财务帐户时,它应该保存交易记录。”在这个测试中,你只关心两个表:accounts和transactions。
accounts
transactions
然而,accounts有一个指向contracts的外键,contracts有一个指向clients的外键,clients有一个指向cities的外键,cities有一个指向states的外键。
contracts
clients
cities
states
现在,数据库将不允许您运行测试,除非在四个与测试无关的表中设置数据。
至少有两种可能的观点:
也可以在运行测试时暂时关闭外键检查。MySQL,至少支持这个。
在这里回答问题的许多人都过于关注通过引用约束实现的引用完整性的重要性。在具有引用完整性的大型数据库上工作性能不佳。Oracle似乎特别不擅长级联删除。我的经验法则是,应用程序永远不应该直接更新数据库,而应该通过存储过程更新。这将代码库保存在数据库中,并意味着数据库保持其完整性。
在许多应用程序可能正在访问数据库的地方,由于引用完整性约束确实会出现问题,但这取决于控件。
还有一个更广泛的问题,应用程序开发人员可能有非常不同的需求,而数据库开发人员可能并不那么熟悉。
在DB2中,如果使用mqt(物化查询表),优化器需要外键约束才能为任何给定的查询选择正确的计划。由于元数据包含基数信息,优化器会大量使用元数据来决定是否使用MQT。
根据我的经验,在数据库关键应用程序中最好避免使用fk。我不反对这里的人说fk是一个很好的实践,但它不实际的数据库是巨大的,有巨大的CRUD操作/秒。我可以不点名地分享……最大的投资银行之一的数据库中没有一个FK。这些约束由程序员在创建涉及DB的应用程序时处理。基本的原因是,当一个新的CRUD完成时,它必须影响多个表,并验证每个插入/更新,虽然这对于影响单行的查询来说不是一个大问题,但当你处理批处理时,它确实会产生巨大的延迟,而任何大银行都必须将批处理作为日常任务。
最好避免fk,但它的风险必须由程序员来处理。
我只知道Oracle数据库,不知道其他数据库,而且我知道外键对于保持数据完整性是必不可少的。在插入数据之前,需要建立一个数据结构,并且建立正确的数据结构。当这一步完成时——所有的主键和外键都创建好了——工作就完成了!
意思是:孤立的行?不。这辈子都没见过。除非一个糟糕的程序员忘记了外键,或者他在另一个层次上实现了外键。在Oracle的环境中,这两者都是巨大的错误,会导致数据复制、孤儿数据,从而导致数据损坏。我无法想象一个没有强制FK的数据库。在我看来是一片混乱。这有点像Unix权限系统:假设每个人都是root用户。想想混乱吧。
外键是必不可少的,就像主键一样。这就像是说:如果我们移除主键会怎样?那么,整个混乱将会发生。这是什么。不能将主键或外键的职责移到编程级别,但必须移到数据级别。
缺点呢?是的,当然!因为在插入时,会有更多的检查。但是,如果数据完整性比性能更重要,那么这是显而易见的。Oracle上的性能问题更多地与索引有关,索引包含PK和FK。
问uite我们经常收到带有FK约束的错误 无法添加或更新子行:外键约束失败 假设有两个表inventory_source和contract_lines,我们引用inventory_source中的contract_lines中的Inventory_source_id,假设我们想要从inventory_source中删除记录,并且该记录已经存在于contract_lines中,或者我们想要从Base表中删除PK列,我们得到FK约束的错误,我们可以使用下面的步骤来避免它
CREATE TABLE inventory_source ( inventory_source_id int(11) NOT NULL AUTO_INCREMENT, display_name varchar(40) NOT NULL, state_id int(11) NOT NULL, PRIMARY KEY (inventory_source_id), KEY state_id (state_id), CONSTRAINT ba_inventory_source_state_fk FOREIGN KEY (state_id) REFERENCES ba_state (state_id) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; CREATE TABLE contract_lines( contract_line_id int(11) NOT NULL AUTO_INCREMENT, inventory_source_id int(11) NULL , PRIMARY KEY (contract_line_id), UNIQUE KEY contract_line_id (contract_line_id), KEY AI_contract_line_id (contract_line_id), KEY contract_lines_inventory_source_fk (inventory_source_id), CONSTRAINT contract_lines_inventory_source_fk FOREIGN KEY (inventory_source_id) REFERENCES ba_inventory_source (inventory_source_id) ) ENGINE=InnoDB AUTO_INCREMENT=135 DEFAULT CHARSET=utf8 ;
我们可以采用以下步骤克服它:-
如果您绝对确信底层数据库系统将来不会改变,我将使用外键来确保数据完整性。
但在现实生活中,还有一个完全不使用外键的很好的理由:
您正在开发一个产品,该产品应该支持不同的数据库系统。
如果您正在使用实体框架(Entity Framework),该框架能够连接到许多不同的数据库系统,那么您可能还希望支持“开源免费”的无服务器数据库。并非所有这些数据库都支持外键规则(更新、删除行……)。
这会导致不同的问题:
1)。在创建或更新数据库结构时,可能会遇到错误。可能只有无声错误,因为数据库系统忽略了外键。
2)。如果依赖于外键,则可能在业务逻辑中较少甚至不进行数据完整性检查。现在,如果新的数据库系统不支持这些外键规则,或者只是以不同的方式运行,那么您必须重写业务逻辑。
您可能会问:谁需要不同的数据库系统?当然,不是每个人都能负担得起或想要在他的机器上安装一个完整的SQL-Server。这是软件,需要维护。其他人已经在其他一些DB系统上投入了时间和金钱。无服务器数据库非常适合只有一台机器上的小客户。
没有人知道,所有这些DB系统是如何运行的,但是您的业务逻辑,包括完整性检查,总是保持不变的。
“在添加记录之前,检查对应的记录是否存在于另一个表中”是业务逻辑。
这里有一些你不希望在数据库中使用它的原因:
如果业务规则更改,则必须更改数据库。数据库在很多情况下需要重新创建索引,这在大型表上很慢。(更改规则包括:允许客人发布消息或允许用户删除他们的帐户,尽管他们已经发布了评论,等等)。
更改数据库并不像通过将更改推到生产存储库来部署软件修复程序那么简单。我们希望尽可能避免更改数据库结构。数据库中的业务逻辑越多,就越有可能需要更改数据库(并触发重新索引)。
TDD。在单元测试中,可以用数据库代替模拟并测试功能。如果您的数据库中有任何业务逻辑,则您没有进行完整的测试,并且需要使用数据库进行测试,或者为了测试目的在代码中复制业务逻辑,复制逻辑并增加逻辑不以相同方式工作的可能性。
使用不同的数据源重用您的逻辑。如果数据库中没有逻辑,我的应用程序可以从数据库中的记录创建对象,从web服务,json文件或任何其他源创建它们。我只需要换出数据映射器实现,就可以对任何源使用我的所有业务逻辑。如果数据库中有逻辑,这是不可能的,您必须在数据映射器层或业务逻辑中实现逻辑。无论哪种方式,在代码中都需要这些检查。如果数据库中没有逻辑,我可以使用不同的数据库或平面文件实现将应用程序部署在不同的位置。
谢谢大家!