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


21

为什么在数据库中应用约束?将其放入代码中是否会更加灵活?

我正在阅读有关实现数据库的初学者书籍,所以我想作为初学者阅读。假设我已经设计了一个数据库,其中包括以下实体模型:

 entity type    |   sub-types
----------------+--------------------------------------------
   Person       |   Employee, Student,       ...
   Student      |   Graduate, Undergraduate, ...
   Employee     |   Teacher,  Administrator, ...

当前限制:

  1. 系统上的注册人只能是学生或雇员。
  2. 人物实体需要社会号码的唯一性,我们假定每个人仅拥有一个唯一的唯一号码(又称足够好的主键)。(请参阅#1)

后来我们决定删除数字1:如果大学决定某天TeacherEmployee子类型)也可以Student在空闲时间上课,那么更改数据库设计就变得更加困难,因为数据库设计可能有成千上万,数百万,数十亿,成千上万的条目,而不仅仅是改变代码的逻辑:只是不允许某人同时注册为学生和雇员的部分。

这是非常不可能的,但我现在想不出其他任何东西。 显然有可能)。

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

#1:7年后的便笺,一个真实的例子:
我见过一个政府,由于错误,已发行的SSN重复了:多个人,同一个SSN。设计原始DB的人肯定犯了没有在数据库中应用这种唯一性约束的错误。(以及后来的原始应用程序中的错误?使用共享数据库的多个应用程序,并且不同意放置,检查和实施约束的位置?...)。
该错误将继续存在于系统中,并且在以后的许多年中所有开发的系统都将依赖该原始系统的数据库。在这里阅读答案,我学会了在数据库中明智(而不是盲目地)应用所有约束(尽可能多地约束),以尽我所能代表真实的物理世界。


2
大多数情况下,我们关心业务规则的实施以及最佳方法。
ypercubeᵀᴹ

3
实际上,您正在提供一个关于约束用途的非常糟糕的示例,因为实体的灵活性和数据库的可扩展性主要是由规范化定义的。话虽这么说,约束是防止任何损坏的数据进入数据库的最后安全措施,即使该应用程序有错误,即使开发了新的应用程序,也即使添加了外部API,即使有人直接编辑数据库,也是如此。约束可以保护数据库,最重要的是,在尝试访问数据库之前,业务逻辑还必须做自己的事情。
Niels Keurentjes

3
实际上,作为一名研究生,我既被视为学生,员工,也被视为老师。因此,您的示例并不是真的不可能。
Winston Ewert

4
您永远不应将数据库设计基于应用程序中的对象。您可以将其设计为人,然后有一个相关的表来定义人的角色。然后问题就不会出现;因为您已经有了角色的分配表,所以人们可以担任多个角色。如果您只想拥有一个角色人,则可以限制该表,以便peopleID是唯一的。当您要更改时,请删除约束。
HLGEM 2013年

对象<->关系映射是一门艺术。
托尔比约恩Ravn的安徒生

Answers:


34

一些约束最好在数据库中实施,而某些约束最好在应用程序中实施。

通常存在于数据库中最佳约束是因为它们是数据模型结构的基础,例如外键约束以确保产品具有有效的约束category_id

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

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

您的示例显示了这样一种情况,即在应用程序中而不是在数据库中进行约束可能更有利,但是也许存在一个基本问题,即初始数据模型过于严格和僵化?


因此,根据此答案,规则<一个人只能存在于Student的子类型表中或仅存在于Employees子类型表中>应在代码中应用,并且数据库具有<Student / Employee子类型必须是有效的人>约束。我对吗?(这是本书的例子)。谢谢。
hkoosha 2013年

2
@loolooyyyy:是的,我认为是正确的。如果数据库执行第一个规则(一个人只能是一个学生或一个雇员),那么您描述的情况(一个雇员要注册一个班级)是不可能的,因为:这个人不能同时存在,并且不能甚至有可能创建第二个“个人”记录,因为他们不能共享大概由第三方(例如政府)发布的社会安全号码。当然,在某些情况下,这种过于严格的数据模型可能会起作用……
FrustratedWithFormsDesigner 2013年

2
@loolooyyyy:使用原始数据模型并仍然让老师成为学生的另一种方法可能是有一个名为的表teachers_as_students,该表是的另一个子类型,Students并且有一个新的外键引用Teachers,并且是系统生成的主键,而不是社交键。安全号码。这样,“学生”实际上是老师的别名,因此老师仍可以注册参加课程。很难确定如果不查看整个数据模型,这种方法的效果如何。
FrustratedWithFormsDesigner 2013年

2
我对此表示反对。没有时间在应用程序中最好地实施约束该答案的语气加权不当。
埃文·卡罗尔

3
@FrustratedWithFormsDesigner当然,它实际上是外键约束的发布者。假设您有三个不同版本/内部版本的数据库访问点的客户端,那么当您停止以红色发送该产品时,该怎么办?您将在哪里存储可能的颜色组合列表?提示:我为您提供了一个集中的位置。而且,如果创建table color_productscolor,则可能会更轻松地创建其他下拉菜单-大多数IDE /模式加载器都支持以下fkey。
埃文·卡罗尔

35

因为:

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

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


4
与(1)和(3)半相关:应用程序代码中的错误可以修复,数据中的错误通常是无法修复的。
亩太短了

17

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

以我个人的观点(基于30多年的数据处理经验以及对数百个用于许多不同目的的不同数据库的经验),任何不将约束置于其所属数据库中的人最终都会拥有不良数据。有时坏数据会导致无法使用。当您的财务/监管数据需要满足特定的审计标准时,尤其如此。


17

在数据库之外实现的大多数参照完整性约束都可以被取消,因此,如果您希望数据始终具有保证的完整性,则必须在数据库中应用约束。句号就这样了。

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

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

顺便说一下,这对于应用程序语言设计者而言并非未知。阅读《Ruby on Rails指南:活动记录验证和回调》中的3.10唯一性

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


16

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

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

准确性 -您未编写的代码将永远不会存在您创建的错误。数据库供应商花费时间来确保其约束代码正确,因此您不必这样做。

速度 -您的应用程序所分发的资源永远不会超过其所基于的数据库。数据库供应商花费时间来确保其约束代码高效,因此您不必这样做。与应用程序效率无关,数据库本身也比应用程序具有更快的数据访问权限。

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

完整性 -应用程序在将数据输入数据库时​​会施加约束,并且需要付出更多的努力才能验证较早的数据是否正确或对数据库中已有的数据进行操作。

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


11

为什么将约束应用于服务器?因为您不能强迫坏人使用您的客户。

要澄清的是,如果您仅在客户端应用程序中进行业务规则处理,则使用其他工具的某人可以连接到数据库服务器,并可以执行他们想要执行的任何操作,而不受任何业务规则和完整性检查的约束。阻止任何人在网络上的任何地方使用任意工具是非常困难的。

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


10

这里有一些很好的答案,并且有可能重复其他想法:

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

在您明确提到的情况下,您突然允许以前不允许的内容,这并不是真正的问题-删除强制实施的任何约束,无论存在于何处。在相反的情况下,突然不再允许老师成为学生,那么无论以前存在什么约束,您都可能有一大堆数据需要清理。


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

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

  3. 应用程序看到读取的一致视图,因此几乎不能保证唯一性。同时数据库还可以看到未提交的数据。


8

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

例外...
如果数据库仅为单个用户存储单个应用程序的数据,例如在大多数Sqlite数据库中,则可能不需要约束。实际上,它们通常不这样做,以便将访问时间保持得如此之快以致无法测量。

对于其他所有内容,
数据库始终提供两个主数据库,我将它们称为编辑器用户

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

用户大多检索数据,并且最关心快速访问毫无疑问的准确信息。他们通常需要各种计数,汇总和清单,这些计数,汇总和清单以前是在那些标志性的,厚如纸的绿条纸打印输出中生成的,但如今通常会出现在当今的网页上。

数据库开发项目几乎总是按照用户的要求启动的,但是设计是受编辑器对数据输入和一次记录的需求驱动的。这样,经验不足的开发人员通常通过不对数据库施加约束来响应对速度(主要是开发速度)的即时需求。

如果将要使用一个应用程序来更改数据库整个生命周期中的数据,并且该应用程序是由一个或少数协调良好的人开发的,那么依靠它可能是合理的。确保数据完整性的应用程序。

但是,正如我们假装的那样,我们可以预测未来,但我们不能。

产生任何数据库的努力都太有价值了,以至于无法丢弃它。就像房子一样,数据库将被多次扩展,更改和翻新。即使完全替换它,所有数据都将迁移到新数据库,同时保留所有旧的业务规则和关系。

约束在数据库引擎本身中以简洁,声明性的形式实现这些规则和关系,可轻松访问它们。没有它们,后续的开发人员将不得不遍历应用程序以对这些规则进行逆向工程。祝好运!

顺便说一下,这正是大型机COBOL程序员必须要做的,因为那些大型数据库通常是在我们有关系引擎和约束之前创建的。即使迁移到IBM DB2之类的现代系统,约束有时也无法完全实现,因为旧规则的逻辑(可能体现在一系列COBOL“批处理”程序中)可能过于复杂,以至于无法转换。相反,可以使用自动化工具将旧的COBOL转换为具有新关系引擎接口的新版本,并进行一些调整,从而保持数据完整性...直到编写新的应用程序,该操作会破坏所有内容并拖垮公司例如,法院禁止了成千上万本不应该拥有的房屋所有者。


7

除了其他评论...

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


5

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

此外,触发器通常不太容易移植,因为它们通常以供应商特定的语言(例如PL / SQL)编写。

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


5
此外,由于读取一致性问题,触发器不能保证完整性。
David Aldridge

3

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

  1. 该数据库可确保不同客户端之间的完整性。您可以在不同平台上让不同的客户端访问数据库。创建新客户端时,数据库中的约束不会冒完整性问题的风险。这样可以避免您在重写或附加访问点的情况下Q / A约束。
  2. 该数据库具有用于构建约束条件的DSL:SQL DDL!
  3. 数据库提供对系统目录中这些约束的访问,因此正确的ORM或“模式加载器”可以读取这些约束并将其引入您的应用程序。例如,如果您的数据库指定您有一个varchar(5)类型,则很有可能找到一种为您的特定语言加载ORM的架构,该架构将语言类型映射到架构的类型,并对其大小进行约束。DBIx for Perl is one such schema loader; 这是实体框架的另一个。这些加载器的功能各不相同,但是它们可以提供的一切都是一个很好的开始,可以确保应用程序的完整性而无需访问数据库。
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.