What's wrong with foreign keys?我记得在播客014中听到乔尔·斯波尔斯基(Joel Spolsky)提到他几乎从未使用过外键(如果我没记错的话)。 但是,对我来说,它们对于避免整个数据库中的重复和后续数据完整性问题至关重要。 人们为什么会有一些扎实的理由(避免按照堆栈溢出原则进行讨论)? 编辑:"我还没有创建外键的理由,所以这可能是我真正设置外键的第一个理由。" 使用外键的原因:
不使用外键的原因:
我认为(我不确定!)大多数已建立的数据库都提供了一种指定未强制执行的外键的方法,该外键只是一些元数据。由于不执行强制措施会清除所有不使用FK的原因,因此,如果第二部分中的任何原因均适用,您可能应该走这条路。 这是一个成长的问题。如果您在教育或职业生涯中的某个地方花了一些时间来喂养和维护数据库(或与做过这项工作的有才华的人紧密合作),那么实体和关系的基本原则在您的思考过程中就根深蒂固。在这些基础知识中,包括如何/何时/为什么在数据库中指定键(主键,外键以及备用键)。这是第二天性。 但是,如果您过去在RDBMS相关工作中没有得到如此全面或积极的经验,那么您可能就不会接触到此类信息。或者,也许您的过去包括沉浸在一个反数据库的环境中(例如,"那些DBA是白痴-我们很少,我们选择了很少的Java / c#代码吊销员可以节省一天的时间"),在这种情况下,您可能会强烈反对听到一些dweeb的神秘bab语,告诉您FK(以及它们可能暗示的约束)确实很重要,如果您只想听的话。 大多数人从小就被教导刷牙很重要。没有它,你能渡过难关吗?当然可以,但是在某些地方,您的牙齿供应会比每餐后刷牙的机会少。如果父母有足够的责任涵盖数据库设计以及口腔卫生,那么我们就不会进行对话。 :-) 我敢肯定,有很多应用程序可以解决它,但这不是最好的主意。您不能总是依靠您的应用程序来正确地管理数据库,并且坦率地说,管理数据库对您的应用程序不是很重要。 如果您正在使用关系数据库,那么似乎应该在其中定义一些关系。不幸的是,这种态度(您不需要外键)似乎已被许多应用程序开发人员所接受,他们不愿为诸如数据完整性之类的愚蠢事情所困扰(但需要这样做,因为他们的公司没有专门的数据库开发人员)。通常在由这些类型组合在一起的数据库中,只有主键才很幸运;) 外键对于任何关系数据库模型都是必不可少的。 我总是使用它们,但是后来我为金融系统创建了数据库。数据库是应用程序的关键部分。如果财务数据库中的数据不完全准确,那么您在代码/前端设计中投入多少精力就无关紧要。您只是在浪费时间。 还有一个事实是,多个系统通常需要直接与数据库连接-从其他仅读取数据的系统(Crystal Reports)到插入数据的系统(不一定使用我设计的API;它可能是由笨拙的经理,他刚刚发现了VBScript并具有SQL框的SA密码。如果数据库不尽如人意,那么-再见数据库。 如果您的数据很重要,那么可以,使用外键,创建一套存储过程来与数据进行交互,并制作出最坚固的数据库。如果您的数据不重要,那么为什么要建立一个数据库呢? 更新:我现在总是使用外键。对于"他们的复杂测试"的异议,我的回答是"编写单元测试,这样他们根本就不需要数据库。使用该数据库的任何测试都应正确使用它,并包括外键。如果设置很麻烦,找到一种更轻松的方法来进行设置。" 外键使自动测试复杂化
假设您正在使用外键。您正在编写一个自动测试,内容为"当我更新财务帐户时,它应该保存交易记录"。在此测试中,您只关心两个表:
但是, 现在,如果不在与测试无关的四个表中设置数据,数据库将不允许您运行测试。 对此至少有两种可能的观点:
在运行测试时,也可能暂时关闭外键检查。至少MySQL支持此功能。 "它们会使删除记录变得更加麻烦-如果其他表中有外键违反该约束的记录,则无法删除"主"记录。"
重要的是要记住,SQL标准定义了删除或更新外键时所采取的操作。
这些相同的操作也适用于 默认值似乎取决于您所使用的SQL Server。 @强调-这正是导致维护噩梦的心态。 为什么?为什么您会忽略声明性参照完整性(可以确保数据至少是一致的),而采用所谓的"软件强制执行",这充其量是一个较弱的预防措施。 不使用它们有一个很好的理由:如果您不了解它们的作用或使用方法。 在错误的情况下,外键约束可能导致意外瀑布式复制。如果有人删除了错误的记录,则将其撤消将成为一项艰巨的任务。 同样,相反地,当您需要删除某些内容时,如果设计不当,约束可能会导致各种阻止您的锁。 没有充分的理由不使用它们……除非我认为孤立行对您来说没什么大不了的。 更大的问题是:您会蒙着眼睛开车吗?如果您开发的系统没有参照约束,便会这样。请记住,业务需求发生了变化,应用程序设计发生了变化,代码中相应的逻辑假设发生了变化,逻辑本身可以重构,等等。通常,数据库中的约束是在当代逻辑假设下放置的,似乎对于特定的一组逻辑断言和假设是正确的。 在应用程序的整个生命周期中,引用检查和数据检查会限制通过应用程序进行的数据收集,尤其是在新需求驱动逻辑应用程序更改时。 对于此清单的主题-从实时事务处理系统的角度来看,外键本身并不"改善性能",也不显着"降低性能"。但是,在HIGH批量"批处理"系统中,用于约束检查的总成本较高。因此,这是实时交易与批处理交易的区别;批处理-顺序检查的批处理的总成本(通过约束检查确保的总成本)会对性能造成影响。 在一个设计良好的系统中,将在处理批处理之前"进行"数据一致性检查(不过,这里也存在相关的成本);因此,在加载期间不需要进行外键约束检查。实际上,包括外键在内的所有约束都应暂时禁用,直到处理批次为止。 查询性能-如果将表连接到外键上,请注意外键列未索引(尽管相应的主键已按定义索引)。为此,通过对外键建立索引,对任何键进行索引,并在已索引的表上连接表有助于提高性能,而不是通过对具有外键约束的未索引键进行联接。 更改主题,如果数据库仅支持网站显示/呈现内容/等并记录点击,则出于这种目的,在所有表上都具有完全约束的数据库将被淘汰。想一想。大多数网站甚至都不使用数据库。对于类似的要求,如果只是说要记录数据而不是每个人都引用数据,请使用没有约束的内存数据库。这并不意味着没有数据模型,没有逻辑模型,但是没有物理数据模型。
使用外键的其他原因:
不使用外键的其他原因: 根据我的经验,最好避免在数据库关键应用程序中使用FK。我不会不同意这里的人,他们说FK是一种很好的做法,但在数据库庞大且每秒CRUD操作数量巨大的情况下,它是不实际的。我可以不命名而分享...最大的投资银行之一在数据库中没有一个FK。这些约束由程序员在创建涉及DB的应用程序时处理。根本原因是,每当执行新的CRUD时,它都必须影响多个表并为每个插入/更新进行验证,尽管对于影响单行的查询而言,这并不是一个大问题,但当您处理时,它确实会造成巨大的延迟大型银行必须将其作为日常任务来进行的批处理。 最好避免使用FK,但其风险必须由程序员处理。 "在添加记录之前,检查另一个表中是否存在对应的记录"是业务逻辑。 以下是一些您不希望在数据库中使用的原因: 如果业务规则发生更改,则必须更改数据库。在很多情况下,数据库将需要重新创建索引,这在大型表上很慢。 (更改规则包括:允许访客发表消息或允许用户尽管发表评论也删除其帐户等)。 更改数据库并不像通过将更改推送到生产存储库来部署软件修订那样容易。我们希望避免尽可能地更改数据库结构。数据库中存在的业务逻辑越多,您就越需要更改数据库(并触发重新索引)。 TDD。在单元测试中,您可以用数据库代替模拟并测试功能。如果您的数据库中有任何业务逻辑,则您未在进行完整的测试,因此需要使用数据库进行测试或出于测试目的而在代码中复制业务逻辑,从而复制逻辑并增加了逻辑无法在数据库中工作的可能性。同样的方式。 在不同的数据源中重用您的逻辑。如果数据库中没有逻辑,则我的应用程序可以从数据库中的记录创建对象,从Web服务,json文件或任何其他来源创建对象。我只需要换出数据映射器实现,就可以将我的所有业务逻辑与任何源一起使用。如果数据库中存在逻辑,那么这是不可能的,您必须在数据映射器层或业务逻辑中实现该逻辑。无论哪种方式,您都需要在代码中进行那些检查。如果数据库中没有逻辑,我可以使用不同的数据库或平面文件实现将应用程序部署在不同的位置。 Clarify数据库是没有主键或外键的商业数据库的示例。 http://www.geekinterview.com/question_details/18869 有趣的是,技术文档不遗余力地解释了表如何关联,使用哪些列来联接它们等。 换句话说,他们可以将表与显式声明(DRI)联接在一起,但他们选择不这样做。 因此,Clarify数据库充满了不一致之处,并且表现不佳。 但是我想这使开发人员的工作更加轻松,而不必编写代码来处理参照完整性,例如在删除,添加之前检查相关行。 我认为,这是在关系数据库中没有外键约束的主要好处。至少从恶魔般的角度来看,它使开发变得更容易。 我同意前面的答案,因为它们对于保持数据一致性很有用。但是,几周前Jeff Atwood发表了一篇有趣的文章,讨论了标准化和一致的数据的利弊。 简而言之,非规范化数据库在处理大量数据时会更快。并且您可能不关心依赖于应用程序的精确一致性,但是它迫使您在处理数据时要更加小心,因为数据库不会。 我只知道Oracle数据库,没有其他数据库,而且我可以说外键对于维护数据完整性至关重要。在插入数据之前,需要制作数据结构并使其正确。完成此操作后-这样就创建了所有主键和外键-工作就完成了! 含义:孤行?不。我一生中从未见过。除非一个糟糕的程序员忘记了外键,否则他是否要在另一个级别上实现它。在Oracle中,两者都是巨大的错误,这将导致数据重复,孤立数据,从而导致数据损坏。我无法想象没有实施FK的数据库。在我看来,这很混乱。有点像Unix许可系统:想象每个人都是root。想想混乱。 就像主键一样,外键是必不可少的。就像在说:如果我们删除主键怎么办?好吧,总会发生混乱。就是那样您不能将主键或外键职责移到编程级别,而必须在数据级别。 缺点 ?是的,一点没错 !因为在插入时,将要进行更多检查。但是,如果数据完整性比性能更重要,那就很容易了。 Oracle上的性能问题更多地与PK和FK附带的索引有关。 我听到的争论是前端应该具有这些业务规则。当您首先不应该允许任何会破坏约束的插入时,外键会"增加不必要的开销"。我同意吗?不,但这是我一直听到的。 编辑:我的猜测是他指的是外键约束,而不是外键概念。 我也听到过这种说法-有些人忘记了在外键上放置索引,然后抱怨某些操作很慢(因为约束检查可以利用任何索引)。综上所述:没有充分的理由不使用外键。所有现代数据库都支持级联删除,因此... 它们会使删除记录变得更加麻烦-如果其他表中有外键违反该约束的记录,则无法删除"主"记录。您可以使用触发器进行级联删除。 如果您不明智地选择了主键,那么更改该值将变得更加复杂。例如,如果我将"客户"表的PK作为该人的名字,并将该键设置为"订单"表中的FK",那么如果客户要更改其名称,那就太麻烦了。但是,这只是伪劣的数据库设计。 我相信使用Fireign Key的优势胜过任何假定的劣势。 验证外键约束需要花费一些CPU时间,因此有些人忽略了外键以获得一些额外的性能。 如果您绝对确定一个基础数据库系统将来不会更改,那么我将使用外键来确保数据完整性。 但是,这是另一个非常现实的理由,根本不使用外键: 您正在开发一种产品,该产品应支持不同的数据库系统。 如果使用的是能够连接到许多不同数据库系统的实体框架,则可能还需要支持"免费的开源"无服务器数据库。并非所有这些数据库都支持您的外键规则(更新,删除行...)。 这可能导致不同的问题: 1.)创建或更新数据库结构时,您可能会遇到错误。也许只会出现静默错误,因为数据库系统只会忽略您的外键。 2.)如果您依赖于外键,则可能会在业务逻辑中进行更少甚至没有数据完整性检查。现在,如果新数据库系统不支持这些外键规则或仅以不同的方式运行,则必须重写业务逻辑。 您可能会问:谁需要不同的数据库系统?好吧,不是每个人都能负担得起或想要一台功能完善的SQL Server。这是软件,需要维护。其他人已经在其他数据库系统上投入了时间和金钱。无服务器数据库非常适合仅在一台计算机上的小型客户。 没有人知道所有这些数据库系统的行为方式,但是经过完整性检查的业务逻辑始终保持不变。 在整个应用程序生命周期中的可维护性和稳定性如何?大多数数据的寿命比使用该数据的应用程序更长。关系和数据完整性非常重要,以至于无法期望下一个开发团队在应用程序代码中正确实现它。如果您尚未使用不尊重自然关系的脏数据来处理数据库,那么您会这样做。数据完整性的重要性将变得非常明显。 我还认为外键在大多数数据库中都是必需的。唯一的缺点(除了强制一致性带来的性能损失外)是拥有外键使人们可以编写假定存在功能性外键的代码。那绝对是不允许的。 例如,我见过人们编写代码,将代码插入到被引用的表中,然后尝试在不验证第一个插入成功的情况下插入到引用表中。如果以后删除外键,则会导致数据库不一致。 您也不能选择对更新或删除采取特定行为。无论是否存在外键,您仍然需要编写代码来执行所需的操作。如果您假定删除不是级联的,则删除将失败。如果您假设对引用列的更新没有传播到引用行,则更新将失败。为了编写代码,您可能不具备这些功能。 如果启用了这些功能,那么您的代码无论如何都会模拟它们,并且会损失一些性能。 因此,摘要...。如果您需要一致的数据库,则外键至关重要。切勿假定外键在您编写的代码中存在或起作用。 我赞同德米特里的答案-很好。 对于那些担心FK经常带来的性能开销的人,有一种方法(在Oracle中)可以让您获得FK约束的查询优化器优势,而无需在插入,删除或更新过程中进行约束验证的开销。那就是用属性RELY DISABLE NOVALIDATE创建FK约束。这意味着查询优化程序ASSUMES,在构建查询时已实施了约束,而数据库并未实际实施约束。在填充带有FK约束的表时,您必须非常小心地承担起责任,以确保您的FK列中没有违反约束的数据,就像您这样做一样。可能会从涉及此FK约束所在的表的查询中获得不可靠的结果。 我通常在数据集市模式中的某些表上使用此策略,但在集成的暂存模式中不使用此策略。我确保要复制数据的表已经强制执行了相同的约束,或者ETL例程强制执行了约束。 在这里,我不得不说第二大部分的意见,外键是确保您拥有完整数据的必要项。 ON DELETE和ON UPDATE的不同选项将使您避开人们在此处提到的有关其使用的"下降"。 我发现在我所有项目的99%中,我将拥有FK来强制数据的完整性,但是,在极少数情况下,我有客户必须保留其旧数据,无论数据有多糟糕。但是后来我花了很多时间编写代码,无论如何它们只能获取有效数据,因此变得毫无意义。 对我来说,如果要遵循ACID标准,拥有外键以确保引用完整性至关重要。 许多在这里回答问题的人都过于依赖通过引用约束实现的引用完整性的重要性。在具有参照完整性的大型数据库上工作效果不佳。 Oracle在级联删除方面似乎特别糟糕。我的经验法则是,应用程序永远不要直接更新数据库,而应该通过存储过程。这将代码库保留在数据库中,并意味着数据库保持其完整性。 在许多应用程序可能正在访问数据库的地方,由于引用完整性约束的确会出现问题,但这取决于控件。 还有一个更广泛的问题是,应用程序开发人员可能有非常不同的要求,数据库开发人员可能不一定熟悉。 像许多事情一样,这是一个权衡。这是您要在何处进行工作以验证数据完整性的问题: (1)使用外键(单点配置表,功能已经实现,测试,证明有效) (2)将其留给数据库的用户(可能有多个用户/应用程序更新同一张表,这意味着更多潜在的故障点并增加了测试的复杂性)。 数据库执行(2)效率更高,易于维护,使用(1)则风险更低。 我一直认为不使用它们是懒惰的。有人告诉我应该总是这样做。但是后来,我没有听乔尔的讨论。我不知道他可能有充分的理由。
我们经常会收到FK约束的错误
我们可以通过以下步骤克服它: 在DB2中,如果使用MQT(材料化查询表),则优化器需要使用外键约束来为任何给定查询选择正确的计划。由于它们包含基数信息,因此优化程序大量使用元数据来使用或不使用MQT。 数据结构设计的一个好原则是确保表或对象的每个属性都受到易于理解的约束。这一点很重要,因为如果您或您的程序可以依靠数据库中的有效数据,那么您就不太可能出现由不良数据引起的程序缺陷。您还可以减少花费的时间来编写代码来处理错误情况,并且更有可能预先编写错误处理代码。 在许多情况下,可以在编译时定义这些约束,在这种情况下,您可以编写过滤器以确保属性始终在范围内,否则保存属性的尝试将失败。 但是,在许多情况下,这些约束可以在运行时更改。例如,您可能有一个"汽车"表,该表具有"颜色"作为属性,该属性最初采用"红色","绿色"和"蓝色"的值。在程序执行期间,可以将有效颜色添加到该初始列表中,并且添加的新"汽车"可以采用最新颜色列表中的任何颜色。此外,您通常希望此更新的颜色列表能够在程序重新启动后继续存在。 为了回答您的问题,事实证明,如果您对数据约束的要求可以在运行时更改,并且这些更改必须在程序重新启动后继续存在,所以外键是解决该问题的最简单,最简洁的方法。开发成本是添加一个表(例如"颜色","汽车"表的外键约束和索引),而运行时成本是对最新颜色的额外表查找验证数据,通常可以通过建立索引和缓存来减少运行时成本。 如果您不使用外键来满足这些要求,则必须编写软件来管理列表,查找有效条目,将其保存到磁盘,如果列表很大,则可以有效地构造数据,并确保对列表的任何更新都不会损坏列表文件,在有多个读取器和/或写入器的情况下,提供对列表的串行访问,依此类推。即,您需要实现许多RDBMS功能。 我会回应德米特里(Dmitriy)所说的话,但要补充一点。 我在一个批处理记帐系统上工作,该系统需要在30多个表中插入大量行。我们不允许做数据泵(Oracle),所以我们必须做批量插入。这些表上有外键,但是我们已经确保它们没有破坏任何关系。 在插入之前,我们禁用外键约束,以使Oracle不会永远花时间进行插入。插入成功后,我们重新启用约束。 PS:在一个大型数据库中,该数据库具有许多外键和单个记录的子行数据,有时外键可能是错误的,并且您可能希望禁止级联删除。对于帐单系统中的我们来说,如果执行级联删除操作,将花费很长时间并且对数据库负担太大,因此我们只在主驱动程序(父)表上使用字段将记录标记为不良。
Wowowo ... 谢谢大家!
FK可能导致您出现问题的一种情况是,即使您不再希望该密钥可用,您仍具有引用该密钥的历史数据(在查找表中)。 在我从事的项目中,通常存在隐式关系而不是显式关系,因此可以在同一列上连接多个表。 取下表 地址
EntityType的可能值可以是Employee,Company,Customer,并且EntityId引用您感兴趣的任何表的primarky键。 我并不真正认为这是做事的最佳方法,但它适用于该项目。 我可以看到一些使用外键的原因(就像有人提到的那样,孤立行很烦人),但我也从未使用过。对于相对健全的数据库架构,我认为不是100%需要它们。约束是好的,但是我认为通过软件强制约束是更好的方法。 亚历克斯 |