我应该在数据库中还是仅在代码中定义表之间的关系?


60

根据我的经验,我过去阅读的许多项目在数据库中都没有关系定义,而是仅在源代码中定义了它们。因此,我想知道在数据库和源代码中定义表之间的关系的优缺点是什么?而更广泛的问题是关于现代数据库中的其他高级功能,例如级联,触发器,过程...我的想法有几点:

在数据库中:

  • 从设计中纠正数据。防止可能导致无效数据的应用程序错误。

  • 在插入/更新数据时减少网络与应用程序的往返路程,因为应用程序必须进行更多查询以检查数据完整性。

在源代码中:

  • 更灵活。

  • 在扩展到多个数据库时更好,因为有时该关系可以是跨数据库的。

  • 更好地控制数据完整性。数据库不必每次应用程序修改数据时都要检查一次(复杂度可以是O(n)或O(n log n)(?))。而是将其委托给应用程序。而且我认为,与使用数据库相比,在应用程序中处理数据完整性将导致更多详细的错误消息。例如:创建API服务器时,如果您在数据库中定义了关系,并且出了一些问题(例如所引用的实体不存在),则会收到一条带有消息的SQL异常。简单的方法是将500返还给客户端一个“内部服务器错误”,并且客户端不知道出了什么问题。或者服务器可以解析消息以找出问题所在,我认为这是一种难看且容易出错的方式。如果您让应用程序处理此问题,

还有别的事吗?

编辑:正如Kilian指出的那样,关于性能和数据完整性的观点非常误导。所以我编辑来纠正我的观点。我完全理解让数据库处理将是一种更有效,更可靠的方法。请检查更新的问题,并对此进行一些思考。

编辑:谢谢大家。我收到的所有答案都指出,约束/关系应在数据库中定义。:)。我还有一个问题,因为它超出了此问题的范围,我将其发布为一个单独的问题:处理API服务器的数据库错误。请留下一些见解。


4
“因为应用程序必须进行更多查询才能检查数据完整性。” 不必要。如果数据库完全在您的应用程序的控制之下,则对数据完整性进行额外的检查可能是过于防御性的编程。您不一定需要它们。只需适当地测试您的应用程序即可确保它仅对数据库进行有效更改。

9
您永远不会忘记一件事:除非所有相关人员都编写完善的软件,否则如果检查包含在软件中,则其中的一项检查将失败并导致约束得不到执行。这不是是否存在的问题,而是何时发生的问题。这将导致难以重现错误,并导致长时间的数据按摩以再次适应软件强制的约束。
达布

10
值得一提的是...一旦您将完整性问题引入数据库,就如同打开Pandora的盒子一样。将完整性重新引入异常缠身的数据库是一场噩梦。对数据库进行严格的控制可能很麻烦,但从长远来看,它将为您节省很多麻烦。
DanK

3
在源代码中:您最终最终编写了大部分数据库。
Blrfl

7
我曾经问过一个非常有才华的程序员,他曾对我说过类似的问题:“这就像在汽车上刹车一样。其目的不是要使汽车行驶更慢,而是要使其行驶得更快更安全。” 确保它可以不受限制地运行,但是如果坏数据以某种方式进入,则可能导致严重的崩溃
Mercurial's

Answers:


70

TL; DR:关系约束应放在数据库中。


您的应用程序不够大。

实际上,您是正确的,跨数据库强制执行关系可能需要在应用程序中强制执行。

但是,我要指出的是,您应该首先检查所使用的数据库软件的文档,然后检查现有产品。例如,在Postgres和MySQL之上有集群功能。

而且,即使你最终需要有一定的应用有效,不与洗澡水一起倒掉了婴儿。毕竟,您要做的事越少,您的生活就越好。

最后,如果您担心将来的可伸缩性问题,恐怕您的应用程序必须进行重大更改,然后才能进行扩展。根据经验,每次增长10倍时,您都必须重新设计...因此,不要让过多的钱浪费在无法预期可伸缩性问题上,而要花钱真正解决这些问题。

您的应用程序不够正确。

您的应用程序有错误执行检查的机会相比,您使用的数据库有什么机会呢?

您最常更改哪一个?

我敢打赌任何时候数据库都是正确的。

您的开发人员没有考虑足够的分布。

在插入/更新数据时减少网络与应用程序的往返行程,因为应用程序必须进行更多查询以检查数据完整性。

红旗1个

如果您在考虑:

  • 检查记录是否存在
  • 如果没有,插入记录

那么您就没有通过最基本的并发问题了:另一个进程/线程可能正在添加记录。

如果您在考虑:

  • 检查记录是否存在
  • 如果没有,插入记录
  • 检查记录是否作为重复插入

那么您就无法考虑MVCC:您拥有的数据库视图是事务开始时的快照;它并没有显示出正在发生,甚至可能没有犯下的所有更新。

跨多个会话维护约束是一个非常困难的问题,很高兴在数据库中解决了该约束。

1 除非您的数据库正确实现了Serializable属性;但实际上很少。


持续:

而且我认为,处理应用程序中的数据完整性将使错误消息比使用数据库更为冗长。例如:创建API服务器时。如果您在数据库中定义了关系,但是出了一些问题(例如所引用的实体不存在),您将收到一条带有消息的SQL异常。

不要解析错误消息,如果使用任何生产级数据库,它应该返回结构化错误。您将至少具有一些错误代码,以指示可能出了什么问题,并根据该代码可以编写适当的错误消息。

请注意,大多数情况下该代码就足够了:如果您有一个错误代码告诉您所引用的外键不存在,则该表很可能只有一个外键,因此您在代码中知道了问题所在。

另外,坦白地说,在大多数情况下,无论如何您都不会优雅地处理错误。只是因为它们太多了,您将无法全部考虑它们...

...只是与上面的正确点有关。每次您看到“ 500:内部服务器错误”是因为触发了数据库约束且未对其进行处理,这意味着数据库已保存您,因为您只是忘记了在代码中对其进行处理。


3
哈哈,你是在我写评论时写的,讽刺地强调了我们俩都在强调这一点。我完全同意:您甚至无法在非DB代码中完成完整性或约束。事务在提交后才能看到其他人的结果(甚至可能看不到)。您可能会得到完整性的错觉,但由于锁,它会受到时间安排或严重的可伸缩性问题的影响。只有数据库才能正确执行此操作。
LoztInSpace

8
所有的优点。另一个是数据库中的关系是自我记录的。如果您曾经不得不对仅在查询它的代码中定义了关系的数据库进行反向工程,那么您会讨厌这样做的任何人。
凯特

1
@ Kat11:是的。自我描述还具有以下优势:工具可以轻松理解数据并对其进行操作,这有时会很有用。
Matthieu M.

1
您对MVCC的争论在正确实现SERIALIZABLE隔离的数据库中是不准确的(例如,现代PostgreSQL可以实现-尽管许多主要的RDBMS都没有)。在这样的数据库中,即使是第一种幼稚的方法也可以正常工作-如果写入冲突,它们将作为序列化失败而回滚。
James_pic '16

1
在正确实现SERIALIZABLE的DB中,如果您处理了所有成功提交的事务,那么就会有一些排序(可能与挂钟排序不同),例如,如果您全部都是串行运行的(没有并发性)按此顺序,所有结果将完全相同。弄错是很难的,而且SQL规范不够明确,以至于您可以确信自己可以允许在SERIALIZABLE级别进行写偏斜,因此许多数据库供应商将SERIALIZABLE视为SNAPSHOT ISOLATION(我在看您的Oracle)。
James_pic '16

119

每次应用程序修改数据时,数据库都不必检查数据完整性。

这是一个严重的误导点。正是出于这个目的而创建数据库。如果您需要数据完整性检查(并且如果您认为不需要它们,则可能会弄错了),那么与应用程序逻辑中进行检查相比,让数据库处理它们几乎肯定更有效且更不易出错。


5
@ dan1111我听不懂您的评论...您是在说:简单的约束是由数据库强制执行的,因此它们对于应用程序代码而言不是问题,更复杂的约束很难实现,因此就放弃吧?还是您说使用数据库触发器(和类似机制)实现复杂的约束太复杂了,因此最好在应用程序代码中实现它们?
巴库里

47
您甚至无法在非DB代码中执行完整性或约束。事务在提交后才能看到其他人的结果(甚至可能看不到)。您可能会得到完整性的错觉,但由于锁,它会受到时间安排或严重的可伸缩性问题的影响。只有数据库才能正确执行此操作。
LoztInSpace

17
有趣的是,根据@LoztInSpace的评论,我曾经在一家(糟糕的)公司工作,其中一个表的设置方式不是让数据库自动递增ID,而是让应用程序获取最后一行ID,向其中添加一个,并将其用作新的ID。不幸的是,约每周一次重复的ID插入使应用程序崩溃停了下来..
Trotski94

9
@ dan1111您永远不会在应用程序中编写错误,对吗?
user253751 '16

4
@DavidPacker我可能会同意,但是一旦有多个客户端访问数据库,就只能在数据库中实施约束。除非那是,否则您将开始批量批发而不是按行锁定表,从而导致性能下降。
艾克尔,2016年

51

约束应该位于您的数据库中,因为(以世界上最好的意愿),您的应用程序将不是访问该数据库的唯一方法。

在某个时候,数据库中可能需要脚本修复,或者您可能需要在部署时将数据从一个表迁移到另一个表。

此外,您可能还会遇到其他要求,例如“大客户X确实需要在今天下午将这些excel数据表导入到我们的应用程序数据库中”,在这种情况下,您将无法适应应用程序代码以适应肮脏的SQL脚本完成时的需求。及时。

这是数据库级完整性将节省您的培根的地方。


此外,请想象一下在您离开后在该公司中担任您角色的开发人员,然后负责更改数据库。

如果数据库中没有FK约束,他会恨您,以便他可以在更改表之前就知道表具有什么关系吗?(线索,答案是肯定的


33
天哪 我不能告诉你怎样,我曾多次向人们解释,一个数据库有多个客户端!即使现在只有一个客户端,也只有一个数据进入系统的途径,基于此假设设计应用程序和架构是Future Yoshi讨厌Past Yoshi的最佳方法。
格雷格·伯格哈特

9
@nikonyrh我不会那样做。存在约束,因此应用程序可以依赖一致的数据。禁用FK“只是为了获得它”是疯狂的。
帕迪

5
同意 同样,即使您的应用程序是唯一的客户端,您也可能会尝试使用不同版本的应用程序来强制实施略有不同的约束。通常会发生狂喜(嗯,不是真的。它更像是“混乱和彻底的挫败感”,而不是“狂喜”)。
艾克尔,2016年

5
我绝对可以证明这一点。就我而言,我被MyISAM卡住了,它实际上不支持外键,因此最终获得了250GB的数据,该数据由应用程序强制执行。当开始修剪数据以使待办事项的规模更易于管理时,以及很显然应用程序本身根本无法处理这些问题时,随之而来的是混乱。我不知道为什么要使用过去时;现在这仍在发生,并且问题(两年后)仍未解决。*嗅探*
明亮度比赛于

1
我认为,一个不错的代码库应该使从应用程序中使用持久层编写一次性脚本的工作至少与编写原始SQL一样快。一次性脚本永远不需要“修改应用程序的代码” 。
乔纳森·

17

您应该在数据库中具有关系。

正如其他答案所指出的,约束检查的性能在该数据库内部要比在应用程序内部好得多。数据库约束检查是数据库擅长的事情之一。

如果您需要额外的灵活性(例如,您提到的跨数据库引用),则可以有意地考虑删除约束。数据库中具有一致性意味着您可以选择修改这些约束以及确定引用完整性的选项。


1
真正。我应该说,在数据库中比在应用程序中更好地处理约束检查的性能。
柯克·布罗德赫斯特

13
  • 我们不再生活在一个后端<->一个前端世界中。
  • 大多数解决方案都涉及Web前端,移动前端,批处理前端和iPad前端等。
  • 数据库引擎已经有成千上万行经过测试的代码行,这些代码行经过优化以增强引用完整性。

当您要编写域问题解决代码时,您真的可以负担得起编写和测试参照完整性强制代码的费用吗?


2
“我们不再生活在一个后端<->一个前端世界中。” 有没有 几年前,我在一个数据库系统上工作,该系统具有用至少两种不同语言编写的程序来访问它。其中一些程序于1970年代首次发布。
Mike Sherrill'Cat Recall'16

2

如果您没有在数据库级别验证数据完整性,约束,关系等,则意味着具有生产数据库访问权限(通过任何其他客户端,包括数据库访问工具)的任何人都更容易混乱数据。

在数据库级别强制实施尽可能严格的数据完整性是一种很好的做法。相信我,随着时间的推移,这将为您节省所有非平凡系统中的巨大麻烦。如果仔细考虑,您还将更快地发现应用程序逻辑错误或业务需求错误和不一致。

对此,请注意,以尽可能规范化和原子化的方式设计数据库。没有“上帝”表。花费大量的精力将数​​据库设计得尽可能简单,理想情况下,要使用许多单独定义的很好的小表,这些表具有单一职责并在所有列上均经过仔细验证。数据库是您数据完整性的最后守护者。它代表城堡的要塞。


1

大多数人从本质上说“是的,总的来说,您总是在数据库中定义关系”。但是,如果计算机科学的学科如此简单,我们将被称为“软件手册阅读器”,而不是“软件工程师”。实际上,我确实同意约束应该进入数据库,除非有充分的理由不应该这样做,所以让我仅提供一些在某些情况下可能被认为是好的理由:

重复码

有时,数据库可以处理的某些功能会自然存在于应用程序代码中。如果向数据库添加类似约束的内容是多余的,那么最好不要重复该功能,因为这违反了DRY原则,并且可能会使使数据库和应用程序代码保持同步的变戏法。

努力

如果您的数据库已经在不使用高级功能的情况下完成了所需要做的工作,则可能需要评估应该将时间,金钱和精力放在何处。如果增加约束可以防止灾难性的失败,从而为您的公司节省很多钱,那么这可能是值得的。如果要添加应该保留的约束,但是已经保证永远不会违反这些约束,那么您将浪费时间并污染您的代码库。这里的保证词是保证

效率

通常这不是一个很好的理由,但是在某些情况下,您可能会有一定的性能要求。如果应用程序代码可以比数据库更快的方式实现某些功能,并且您需要额外的性能,则可能需要在应用程序代码中实现该功能。

控制

与效率有关。有时您需要关于功能实现方式的极其精细的控制,有时需要数据库处理它才能将其隐藏在需要打开的黑匣子后面。

结束点

  • 数据库是用代码编写的。他们所做的没有什么神奇的事情是您无法在自己的代码中完成的。
  • 没有什么是免费的。约束,关系等均使用CPU周期。
  • 没有传统的关系功能,NoSQL世界中的人们相处得很好。例如,在MongoDB中,JSON文档的结构足以支持整个数据库。
  • 盲目且无知地使用高级数据库功能无法保证任何好处。您可能会无意间使某项工作生效,但后来又破坏了它。
  • 您提出了一个非常笼统的问题,没有列出特定的要求或约束。您问题的真正答案是“取决于情况”。
  • 您没有指定这是否是企业规模的问题。其他答案是谈论诸如客户和数据完整性之类的事情,但是有时这些事情并不重要。
  • 我假设您正在谈论传统的SQL关系数据库。
  • 我的观点来自于在小型(最多50个表)项目中不再使用大量约束和外键,并且没有注意到任何缺点

我要说的最后一件事是,您将知道是否不应该将功能放在数据库中。如果不确定,则最好使用数据库功能,因为它们通常可以很好地工作。


1
如果人们因不同意他们的教条而拒绝了深思熟虑的答案,那么SE StackExchange就会变得更糟。
卡尔·莱斯

5
这个答案的前提是,在某些情况下,您可能会将约束排除在数据库之外是有效的,但解释不力且会引起误解。尽管我同意对于某些约束来说数据库并不是最佳选择,但是关系数据库应该没有基本键和引用完整性约束。零例外。每个数据库都将需要主键,而绝大多数将需要外键。这些应该始终由数据库强制执行,即使它重复逻辑也是如此。你掩饰这一事实的原因是我投反对票的原因。
jpmc26 '16

1
“数据库是用代码编写的。它们所做的任何神奇的事情都是您自己的代码无法做到的。” ,不,您不能在应用程序代码中强制执行引用完整性(如果不需要强制执行引用完整性,为什么还要使用数据库服务器?)。这不是什么代码可以做的,它是关于地方是可以做到的。
海德

0

与往常一样,有很多答案。对我来说,我找到了一条简单的规则(它仅适用于以模型为中心的方法)。通常,我只关注应用程序的不同层。

如果模型由多个实体组成,并且实体之间存在依赖关系,则持久层应以可能的方式反映这些依赖关系。因此,如果您使用的是RDBMS,则还应该使用外键。原因很简单。这样,数据始终在结构上有效。

在此持久性层上进行工作的任何实例都可以依靠它。我假设您正在通过接口(服务)封装此层。因此,这是设计结束和现实世界开始的地方。

查看您的观点,尤其是跨数据库引用。在这种情况下,是的,不应在RDBMS本身中而是在服务中实现引用。但是在采用这种方式之前,在设计过程中考虑一下是否会更好?

意味着,如果我已经知道,有些零件需要存储在不同的数据库中,那么我可以将它们放在那儿并将其定义为单独的模型。对?

您还指出,在代码中实现此功能更加灵活。是的,但这听起来不像您要处理的设计不完整吗?问问自己,为什么需要更大的灵活性?

由于数据库中完整性检查,性能问题不是真实的。RDBMS可以比您的任何实现更快地检查这些事情。为什么?好吧,您必须处理媒体中断,RDBMS则不必。而且它可以通过使用aso统计信息来优化此类检查

如此看来,一切都回到了设计上。当然,您现在可以说,但是如果出现未知要求,那么改变游戏规则又该怎么办?是的,它可能会发生,但是此类更改也应进行设计和计划。; o)


0

您有很好的答案,但还有更多要点

数据完整性是数据库的设计目标

在应用程序级别执行像FK删除这样的适当的并发性是可怕的

DBA具有数据完整性方面的专业知识

在程序级别,您可以插入,更新,批量更新,批量插入,批量删除...
瘦客户端,胖客户端,移动客户端....
数据完整性不是程序员的专业知识-重复的代码很多,有人会搞砸它起来

假设您被黑了-两种方式都遇到了麻烦,但是如果数据库中没有完整性保护,那么黑客可以通过一个小漏洞造成很多破坏

您可能需要直接通过SQL或TSQL操作数据
没有人会记住所有数据规则


0

您的问题没有道理:如果可以更改数据库,则为代码,如果不能更改数据库,则必须在其他位置创建约束。

您可以更改的数据库的代码与ruby,javascript,c#或ada的任何行一样多。

关于在系统中放置约束的问题应该归结为可靠性,成本和易于开发。


0

这里有很多好的答案。我要补充一点,如果您有用Y语言编写的应用程序,则可以在Y中创建类似数据库约束的代码。然后有人想要使用Z语言访问您的数据库,则必须再次编写相同的代码。如果实现方式不完全相同,上帝会帮助您。或者,当有知识的业务用户使用Microsoft Access连接到您的数据库时。

我的经验告诉我,当人们不想使用数据库约束时,这是因为他们实际上是在尝试以错误的方式进行操作。例如,他们正试图批量加载数据,并且他们希望暂时将非空列保留为空。他们打算“以后再解决”,因为使非空约束变得至关重要的情况“在这种情况下不可能发生”。另一个例子可能是当他们试图将两种不同类型的数据插入同一张表时。

更有经验的人会退后一步,找到不涉及尝试绕过约束的解决方案。当然,解决方案可能只是约束不再适用,因为业务发生了变化。

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.