为什么在数据库中而不是在代码中应用约束?

hko*_*sha 24 database-design

为什么在数据库中应用约束?放在代码里不是更灵活吗?

我正在阅读一本关于实现数据库的初学者书籍,所以我作为初学者来问这个问题。假设我设计了一个数据库,包括这个实体模型:

 entity type    |   sub-types
----------------+--------------------------------------------
   Person       |   Employee, Student,       ...
   Student      |   Graduate, Undergraduate, ...
   Employee     |   Teacher,  Administrator, ...
Run Code Online (Sandbox Code Playgroud)

当前限制:

  1. 系统上的注册人员只能是学生或员工。
  2. 人实体需要社会编号的唯一性,我们假设每个人都只有一个唯一的(也就是一个足够好的主键)。(见#1)

后来我们决定去掉数字 1:如果有一天学院决定TeacherEmployee子类型)也可以Student,在空闲时间参加课程,改变数据库设计要困难得多,可能有数千、数百万、数十亿,数以万计的条目,而不仅仅是更改代码中的逻辑:只是不允许一个人同时注册为学生和员工的部分。

(这是非常不可能的,但我现在想不出其他任何事情。 显然这是可能的)。

为什么我们在数据库设计而不是代码中关心业务规则?

#1:7 年后的一个笔记,一个真实的例子:
我见过一个政府,因为一个错误,发放的 SSN 被复制了:多人,同一个 SSN。那些设计原始数据库的人肯定犯了没有在数据库中应用这种唯一性约束的错误。(后来原始应用程序中的一个错误?多个应用程序使用共享数据库并且不同意在哪里放置、检查和强制执行约束?...)。
这个错误将继续存在于系统中之后开发的所有系统都依赖于原始系统的数据库,未来很多年。阅读这里的答案,我学会了在数据库中明智地(而不是盲目地)应用所有约束,尽可能多地应用它们,以尽可能好地表示那里的真实物理世界。

Col*_*art 38

因为:

  1. 我希望数据库中的所有数据都受相同的约束,而不仅仅是数据受今天运行的代码版本中的约束。
  2. 我想要声明性约束,而不是程序性约束。
  3. 数据库中的数据通常比今天为与其交互而编写的代码寿命更长。而这些数据——而不是代码——是组织的资产。
  4. 当我知道所有数据都受到严格约束时,我的代码变得更加简单。我不再需要考虑我知道数据库保证不可能的特殊情况。

只是一些对我很重要的原因。

  • 与 (1) 和 (3) 半相关:应用程序代码中的错误可以修复,数据中的错误通常无法修复。 (5认同)

Fru*_*ner 35

有些约束最好在数据库中执行,有些则最好在应用程序中执行。

最好在数据库中执行的约束通常存在,因为它们是数据模型结构的基础,例如用于确保产品具有有效category_id.

在应用程序中强制执行的约束可能不是数据模型的基础,例如所有 FooBar 产品都必须是蓝色的——但后来有人可能会决定 FooBar 也可以是黄色的。这是不需要在数据库中的应用程序逻辑,尽管您可以创建一个单独的colours表,并且数据库可以要求产品引用该表中的有效条目。但是,唯一的记录colours具有该值的blue决定仍然来自数据库之外的某个地方。

考虑一下如果您在数据库中没有约束并要求在应用程序中强制执行所有约束会发生什么。如果您有多个应用程序需要处理数据,会发生什么?如果不同的应用程序决定以不同的方式强制执行约束,您的数据会是什么样子?

您的示例显示了在应用程序中而不是在数据库中设置约束可能更有益的情况,但也许初始数据模型过于严格和不灵活存在根本问题?

  • @loolooyyyy:是的,我认为这是正确的。如果数据库强制执行第一条规则(一个人只能*是学生或员工),那么你描述的情况(员工想要注册一个班级)是不可能的,因为:这个人不能同时是,并且甚至不可能创建第二个“个人”记录,因为他们无法共享可能由第三方(例如政府)颁发的社会安全号码。当然,这种过于严格的数据模型可能适用于某些情况...... (3认同)
  • @loolooyyyy:另一种使用原始数据模型并仍然让教师成为学生的方法可能是有另一个名为 `teachers_as_students` 的表,它是 `Students` 的另一个子类型,有一个新的外键引用 `Teachers`,以及一个 *系统生成的* 主键,而不是社会安全号码。这样,“学生”实际上是老师的别名,因此老师仍然可以注册上课。如果没有看到整个数据模型,很难确定这将如何工作。 (3认同)
  • @FrustratedWithFormsDesigner 当然,它实际上是外键约束的典型代表。假设您有三个不同版本/构建的 db 接入点客户端,当您停止以红色交付该产品时,您将做什么?你打算在哪里存储*可能的*颜色组合列表?提示:我有一个集中的地方供你使用。如果您创建表 `color_products` 和 `color`,您可能能够更轻松地创建额外的下拉列表——大多数 IDE/模式加载器,支持以下 fkeys。 (3认同)
  • 我投了反对票。没有时间在应用程序中最好地强制执行约束*。* 这个答案的语气加权不当。 (2认同)

HLG*_*GEM 20

数据可能会比应用程序代码存活的时间更长。如果规则对数据随着时间的推移有用(例如有助于保持数据完整性的外键约束)至关重要,则它必须在数据库中。否则,您可能会在访问数据库的新应用程序中丢失约束。不仅多个应用程序会访问数据库(包括一些可能没有意识到存在重要数据规则的应用程序),而且其中一些应用程序(例如数据导入或报告应用程序)可能无法使用在主数据输入应用程序中设置的数据层。坦率地说,根据我的经验,应用程序代码中约束中存在错误的可能性要高得多。

在我个人看来(基于 30 多年的数据处理经验和数百个用于许多不同目的的不同数据库的经验),任何没有将约束放入它们所属的数据库中的人最终都会得到糟糕的数据。有时坏数据到无法使用的地步。当您拥有需要满足特定审计标准的财务/监管数据时尤其如此。


Dav*_*dge 18

大多数在数​​据库外部实现的参照完整性约束都可以被破坏,因此如果您希望数据始终保证完整性,那么您必须在数据库中应用约束。句号,就是这样。

通常,通过数据库读取一致性机制会破坏应用程序级别的约束,通过该机制会话在提交之前无法查看其他会话的数据。

例如,两个会话可以尝试将相同的值插入到旨在唯一的列中。他们既可以检查在同一时间,该值已经不存在,既可以插入自己的价值,并能提交双方。在数据库中实现的唯一约束不会让这种情况发生。

顺便说一下,这对应用程序语言设计者来说并不陌生。阅读Ruby on Rails 指南中的第3.10唯一性:Active Record Validations and Callbacks

这个助手在对象被保存之前验证属性的值是唯一的。它不会在数据库中创建唯一性约束,因此可能会发生两个不同的数据库连接为您打算唯一的列创建两个具有相同值的记录。为避免这种情况,您必须在数据库中创建唯一索引。


Lei*_*fel 17

数据库强制执行约束的好处:

简单性- 声明约束比声明约束并编写将强制执行该声明的代码要简单得多。

准确性- 您没有编写的代码永远不会有您创建的错误。数据库供应商会花时间确保他们的约束代码准确无误,因此您不必这样做。

速度- 您的应用程序的分布永远不会超过它所基于的数据库。数据库供应商花时间确保他们的约束代码是有效的,因此您不必这样做。数据库本身对数据的访问速度也比应用程序无论多么高效都快。

重用- 您可以从一个平台上的一个应用程序开始,但它可能不会一直这样。如果您需要从不同的操作系统、不同的硬件或语音接口访问数据怎么办?通过在数据库中设置约束,无需为新平台重新编写此代码,也无需为准确性进行调试或为速度进行配置。

完整性- 当数据输入数据库时​​,应用程序会强制执行约束,并且需要额外的努力来验证旧数据的准确性或操作数据库中已有的数据。

长寿- 您的数据库平台可能会比任何特定应用程序寿命更长。


Gre*_*ker 12

为什么要在服务器上应用约束?因为你不能强迫坏人使用你的客户端。

澄清一下,如果您只在客户端应用程序中进行业务规则处理,那么使用其他工具的人可以连接到数据库服务器并做他们想做的任何事情,而不受任何业务规则和完整性检查的限制。阻止任何人在网络上的任何地方使用任意工具是非常困难的。

如果您在数据库服务器上进行完整性检查,那么每次访问数据的尝试,无论使用何种工具,都将受到您的规则的约束。


Aar*_*and 11

这里有一些很好的答案,并且冒着重复其他想法的风险:

  • SSN是不是一定是唯一的。哎呀,SSN 甚至并不总是为人所知,并且在某些情况下它不存在(还)。SSN 可以重复使用,并非所有员工或学生都可能拥有 SSN。这是问题的外围内容,但表明,无论您在哪里强制执行约束,您都需要非常彻底地了解数据模型和域才能做出有关业务规则的决策。
  • 我个人更喜欢约束尽可能接近数据。原因很简单,并不是每个人都会使用应用程序代码来更改数据库中的数据。如果您在应用程序级别强制执行业务规则,而我UPDATE直接针对数据库运行一条语句,您的应用程序如何防止无效更改?应用程序中业务规则的另一个问题是重新编译/重新部署可能很困难,特别是对于分布式应用程序,可能不是每个人都能同时获得更新。最后,更改应用程序中的业务规则对违反新规则的已经存在的数据完全没有作用——如果向数据添加新约束,则需要修复数据。
  • 您可以证明在各个级别进行多次冗余检查是合理的。这一切都取决于部署方法的灵活性、更改的可能性以及同步数据库和其他层中的业务规则更改的难度。在应用程序层重复检查的一个令人信服的论点是,您可以潜在地阻止到数据库的往返,但只会使那里的约束失败(取决于约束的性质以及它是否依赖于现有数据)。但是,如果我必须选择其中之一,我会将其放入数据库中,原因是上述原因。

在您明确提到的情况下,您突然允许以前不允许的东西,这并不是真正的问题 - 您删除任何强制执行它的约束,无论它存在于何处。在相反的情况下,突然不再允许教师成为学生,您可能需要清理大量数据,无论先前存在何处约束。


ibr*_*041 10

  1. 数据库可以有效地检查约束。比代码好。

  2. 完整性约束帮助数据库找到有效的执行计划

  3. 应用程序看到读取一致的视图,因此它很难保证唯一性。而数据库也可以看到未提交的数据。


小智 9

简短回答...以保持数据完整性(即准确性和有效性)。

一个例外...
如果数据库只是为单个用户存储单个应用程序的数据,例如在大多数 Sqlite 数据库中,它可能不需要约束。事实上,他们通常不这样做,以保持访问时间如此之快,以至于无法衡量。

对于其他一切......
数据库总是为两个主人服务,我称之为editorsusers

编辑者大多将数据放入数据库,一次检索一条或少量记录。他们的主要关注点是快速、准确地访问所有相关数据以及快速、可靠地存储其更改。

用户主要是检索数据,并且最关心快速访问毫无疑问的准确信息。他们经常需要各种计数、聚合和列表,这些计数、聚合和列表过去是在那些标志性的一英尺厚的绿条纸打印输出中生成的,但今天通常会出现在网页上。

数据库开发项目几乎都是在授意下开始了用户,但设计得到由数据输入和记录在-A-时间的需求驱动的编辑。因此,缺乏经验的开发人员通常会通过不在数据库中设置约束来响应对速度(主要是开发速度)的迫切需求。

如果有且仅有一个应用程序曾经打算用来更改的数据整个生命的数据库,并且该应用程序是由一个或少数几个很好的协调个人的发展,那么它可能是合理的依靠确保数据完整性的应用程序。

然而,尽管我们假装可以预测未来,但我们做不到。

生产任何数据库的努力都太有价值了,不能把它扔掉。数据库就像房子一样,会被多次扩展、改变和翻新。即使完全替换,所有数据也会迁移到新数据库,同时保留所有旧的业务规则和关系。

约束在数据库引擎本身中以简明的、声明性的形式实现这些规则和关系,以便于访问它们。如果没有它们,后续的开发人员将不得不通过应用程序来对这些规则进行逆向工程。祝你好运!

顺便说一下,这正是大型机 COBOL 程序员必须做的事情,因为这些海量数据库通常是在我们拥有关系引擎和约束之前创建的。即使迁移到像 IBM 的 DB2 这样的现代系统,约束有时也不会完全实现,因为可能包含在一系列 COBOL“批处理”程序中的旧规则的逻辑可能非常复杂,以至于无法进行转换。相反,可以使用自动化工具将旧的 COBOL 转换为带有新关系引擎接口的新版本,并通过一些调整,保持数据完整性......诉诸法庭,例如,取消数千名他们不应该拥有的房主的赎回权。


gsi*_*ems 8

除了其他评论...

如果/当您有一个数据库,其中任何给定的表可以由一个或多个应用程序或代码路径更新,那么在数据库中放置适当的约束意味着您的应用程序不会复制“相同”的约束代码。这通过简化维护(如果/当数据模型发生更改时减少要更改的位置数量)并确保无论应用程序更新数据如何都始终如一地应用约束,从而使您受益。


小智 6

就我个人而言,我认为创建和更改约束比创建触发器更容易,例如,这是使用源代码强制执行业务规则的一种方法。

触发器也不太可能是可移植的,因为它们通常是用供应商特定的语言编写的,例如 PL/SQL。

但是,如果约束不能满足您的需求,您始终可以使用触发器来强制执行您的业务规则。

  • 由于读取一致性问题,触发器也不能保证完整性。 (5认同)

Eva*_*oll 5

他们应该总是在数据库应用首先是因为,

  1. 数据库确保不同客户端之间的完整性。您可以让不同平台上的不同客户端访问数据库。创建新客户端时,数据库中的约束不会带来完整性问题的风险。这使您不必在重写或附加访问点的情况下对约束进行问答。
  2. 数据库有一个用于构建约束的 DSL:SQL DDL!
  3. 数据库提供对系统目录中这些约束的访问,因此适当的 ORM 或“模式加载器”可以读取这些约束并将它们带入您的应用程序。例如,如果您的数据库指定您有一个varchar(5)类型,那么您很有可能为您的特定语言找到一个架构加载 ORM,它将语言类型映射到架构的类型,并组装它自己对大小的约束。DBIx for Perl is one such schema loader; 这是Entity Framework 的另一个。这些加载器的功能各不相同,但它们可以提供的任何功能都是确保应用程序完整性的良好开端,而无需访问数据库。