【问题标题】:Should you enforce constraints at the database level as well as the application level?您是否应该在数据库级别和应用程序级别强制执行约束?
【发布时间】:2010-10-02 14:50:22
【问题描述】:

我一直在阅读 Dan Chak 的“Enterprise Rails”一书,它让我思考:您是否认为您应该在数据库级别和应用程序级别都有数据约束?或者您是否觉得与 Ruby on Rails 之类的固执己见的框架相似——数据库只是数据的“愚蠢存储库”,所有检查都应该在你的应用程序中完成(我不想在这里单独列出 RoR——我是我自己是 Rails 的忠实粉丝,但我不同意它对数据库的处理方式)?

就个人而言,我认为您应该同时拥有它们,以确保您的数据库和应用程序得到很好的保护。我的意思是你应该使用非空约束,如果知道的话,给你的字段一个长度(而不是将它们全部留在 nvarchar(255) ),有诸如 之类的东西外键检查约束触发器在您的数据库上,然后还通过应用程序中的业务逻辑规则强制执行。 IMO 这使您的应用程序通过其用户界面变得健壮,并且还可以防止可能直接访问数据库的人。

我最常看到的反驳是,它需要相当于重复逻辑的东西;一次在数据库级别,一次在应用程序级别 -- 假设您有一个检查约束来验证是否输入了产品的 SKU(即它的长度大于零)。

您现在还需要在业务逻辑中包含验证方法,以确保用户输入的值的长度大于零,还可能需要一些客户端 Javascript 在用户键入数据时捕获错误。

我并不认为这是一件坏事 - 是的,您有一些重复的逻辑,但最终结果是“数据库作为堡垒”的心态,因为如果您考虑一下,您的数据是唯一最重要的部分你的申请;毕竟,如果数据很容易被破坏和破坏,那么您闪亮的新 Web 2.0 应用程序有什么用?

您对此有何看法?数据库应该是像诺克斯堡这样的坚不可摧的堡垒,还是一个由激光守卫的开放式保险箱?换句话说,您应该牺牲一些重复的逻辑来确保数据模型的安全,还是将所有内容留给您的应用程序并仅使用数据库来存储数据?

【问题讨论】:

  • 在 OO 中,除了对象之外,您还使用其他语言来维护其自身的完整性。我希望数据库也能做到这一点。
  • Database constraints are law; application logic constraints are advice.
  • 我什至会走得更远,@Lukasz,它们是不能被打破的定律(想想热力学第二定律而不是调节车速的定律)。

标签: database-design


【解决方案1】:

简而言之:数据库应该强制执行约束。

为什么:

  1. 更容易。 例如。要在特定数据列上设置约束,只有一个地方可以设置它:列本身。数据可能来自不同的来源,但检查是在数据最终被搁置的地方进行的。
  2. 完整性。 数据库应对其托管的数据负责。不一致的数据库与没有数据库一样好。
  3. 灵活性。 新的 UI 开发环境来得太频繁了。如果数据库主动说它会处理约束,那么前端开发和功能测试就更容易了。

【讨论】:

    【解决方案2】:

    是的,如果您想限制数据库中的内容。这些层应该尽可能地相互区分,并且您的数据库不应依赖另一个层来确保它遵循规则。

    无法保证有缺陷(或恶意)的“业务逻辑”层不会将有害数据插入您的表中。当然,如果您可以信任其他层,则可能不需要它。但是我在一家大型机商店工作,那里的 DBA 总是不得不解决由年轻的 Java 鞭打者在没有充分(任何?)测试的情况下将错误代码投入生产所引起的问题 :-)。

    在不同开发区域之间共享的数据库表(这就是我们的全部)应该始终保护自己免受错误数据的影响。当应用 A 将不可靠的数据放入应用 B 使用的表中时,受热的不是应用 A 的开发人员,而是 DBA。

    【讨论】:

    • 您所说的示例还有另一个问题。为什么测试不是自动化的,为什么 juniorslive server 有部署权限?
    【解决方案3】:

    如果您遵循 Jeff Atwood 学派的观点,数据库只是一个愚蠢的数据存储和检索系统,那么您会将所有验证都放在应用程序层中。

    但是,我发现应用程序就像小孩子一样。不加检查,他们会把房间里的所有东西都扔掉。这将由父母来收拾烂摊子。在这种情况下,将由 DBA 进行清理。

    但是,我认为您需要小心使用每个数据库数据完整性功能,因为它就在那里。使用外键约束和触发器重载数据库可能会产生比您想象的更多的问题。我倾向于只在关系非常密切的表上使用外键,例如表头/明细表对。如果您开始在任何地方添加外键,您最终可能会得到一个难以想象的数据库。

    我很少使用触发器。我认为他们使数据库非常不透明。您发出一个简单的更新/插入/删除命令,可能会发生奇怪的事情。我想有两个地方是不可避免的:

    1. 当您没有写入数据库的应用程序的源代码并且您需要修改行为时。触发器是您唯一的选择。

    2. 如果您正在对视图执行 CRUD 操作。插入/更新/删除操作必须使用触发器。

    我倾向于在应用程序中执行基本验证。通过这种方式,用户可以立即得到反馈,指出出现了问题。需要查找相关表的复杂验证可能最好在数据库中完成(以及应用程序所做的简单验证)。我认为,如果不使用复杂的锁定策略,某些形式的验证几乎不可能在应用程序级别得到保证。

    如果您有多个应用程序,可能在不同平台上以不同语言编写,那么将更多验证放入数据库层的情况会得到加强。由不同程序员编写的两个或多个应用程序执行相同验证的可能性相当小。最好在一个地方完成。

    这个世界的 Jeff Atwoods 会建议您编写一个所有应用程序都用来与之通信的 Web 服务。 Web 服务执行数据验证。这样做可以使数据库保持为哑存储容器,从而使您能够切换数据库引擎。实际上,您很少更改数据库引擎(除非您开始使用 Microsoft Access!)。如果您编写 Web 服务纯粹是为了集中您的数据验证,那么我认为您太过分了。

    【讨论】:

    • 我认为这表明您对自己的应用程序的期望明显很低;如果他们是,那么你必须处理它。但是,我认为这不应该是您的目标。
    • 我无法想象你会如何故意用触发器改变功能。
    • Jeff Atwood 学派的“数据库只是一个愚蠢的数据存储和检索系统”——很确定我从来没有这么说过。除非你指的是另一个 Jeff Atwood
    • 杰夫,我很抱歉使用引号。我不是有意引用你的话。我只是指您不喜欢存储过程。如果您从数据库中删除所有存储过程,您几乎只剩下一个基本的 CRUD 存储库。
    • 就是这样,杰夫,你现在被这句话标记为终身。我现在可以听到谷歌在吸吮文本:-)。
    【解决方案4】:

    如果您确定永远不会有另一个客户端应用程序,则可以将数据库视为一个简单的存储。但是,如果您将拥有多个客户端应用程序,显然您将不得不在所有客户端应用程序中复制约束,这是一个坏主意。请记住,其他客户端包括开发人员工具。

    此外,通过将数据库用作“哑存储库”,您很可能最终会得到一个效率较低的应用程序。数据库可以比您的应用程序更有效地做很多事情。为什么不利用它呢?

    【讨论】:

      【解决方案5】:

      通常总是存在一些重复,并且数据库不仅仅是愚蠢的存储库。

      分贝

      数据库确保数据级别的完整性。外键约束、非空约束、大小约束都基本被数据库覆盖了。

      你不能在数据库中做所有事情,但你可以做很多事情。保护数据。

      业务层

      向上移动一个级别,您就有了业务逻辑。通常这是您与其他应用程序(Web 服务、您自己的 UI 等)的集成点。这里将业务逻辑编码到应用程序中。例如,如果产品的结束日期为 x,那么如果 y 有不同的结束日期,它就不会出现在 y 中。

      很难在数据库中描述这种规则,所以你不要。但是你的业务逻辑层仍然会拦截它知道是无效的东西。例如如果描述字段不允许为空,业务逻辑不应该将其发送到数据库。无论如何它都会出错,但是您会尝试在已知错误的地方拦截它们。

      在数据库中也很难表达其他“规则”,例如“如果新用户来自阿肯色州,他们的有效期为 1 年,否则为 2 年,除非他们有 3 个孩子,其中一个名叫 Barry”。我们可以嘲笑这个例子,但一个经验丰富的程序员会告诉你,业务逻辑是最大的矛盾之一。

      用户界面

      升级到 UI,UI 还定期在屏幕中编码业务逻辑。表单和其他页面通常会以无效状态存在,至少大部分时间了解规则是 UI 的工作。希望 UI 将逻辑推迟到业务层,但业务层不知道字段 1 是截止日期,字段 2 是描述。

      如果用户已经选择了小部件 Y,则 UI 知道使用 X 搜索产品。 UI 知道需要描述,并且项目计数 > 0 且

      在 Web UI 中,我们还添加了再次复制服务器代码中的逻辑的客户端脚本。我们使用客户端脚本来获得更好的用户体验,但最终不要相信来自客户端的任何东西(可以关闭脚本,手动操作表单字段等)


      所以你可以看到逻辑会重复。你试图尽可能地减少重复,但实际上对于一个非平凡的程序来说这几乎是不可能的。

      【讨论】:

        【解决方案6】:

        我认为您应该不惜一切代价尝试保护您的数据。没有什么比尝试报告由于应用程序存在错误而具有不良数据的应用程序更糟糕的了。现在这是什么意思?

        您应该通过 FK 来加强您的关系,没有理由不这样做。您应该尽量避免使用空值,并且仅在适当的空值时使用它们。不过,我确实认为有一条细线。

        您是否应该解析电话号码以确保其格式正确?可能不会,但话又说回来,您可能应该将电话号码存储在没有格式问题的模式中。

        【讨论】:

          【解决方案7】:

          如果您没有在数据库中强制执行至少基本的完整性,则在某些时候无效数据会进入。可能来自应用程序。错误,可能来自某人拉起 SQL 控制台,无论如何。然后你会发现,当不可能的事情发生时(“所有 B 记录都必须有 A 记录!你的意思是它不存在是什么意思?”)发生时,你的应用会出现有趣的故障模式。

          仅当应用程序是唯一会触及该数据库的事物时,在应用程序中强制执行完整性才有效。即便如此,你还是要小心应用程序的错误,因为应用程序比架构大得多。

          【讨论】:

            【解决方案8】:

            为了提供稍微不同的观点,我认为这取决于上下文。

            首先,多年来,我已经从 100% 确信在 DBMS 中实施约束转变为试图完全避免它:我希望我的所有业务逻辑都在同一层上。

            其次,我经常使用 Rails 和 ActiveRecord 迁移,不允许在字段大小和 NULL_ness 之外定义太多的 db-resident 约束。

            如果您正在构建一个新应用程序,其中包含一个完全专用于您的应用程序的新数据库,那么您可以在应用程序代码中强制执行您的约束,作为业务逻辑的一部分。这就是 Rails 喜欢的工作方式,而且它似乎工作得很好。

            如果您要针对在数据库约束中实现业务规则的旧数据库构建新应用程序,那么我建议您继续这样做,并在业务层中接受一些重复的验证规则。最好在尝试将数据应用到数据库之前检查数据是否有效。

            如果您正在构建一个新的应用程序/数据库,并期望其他应用程序能够访问数据,那么该方法将取决于这些其他应用程序的构建方式。同样,在 Rails 中,您可能应该寻找共享模型的方法,在这种情况下,该层就足够了。如果你不能拒绝其他实现方式直接访问你的数据,那么你又回到了重复。我的偏好是尝试 - 非常努力地 - 拒绝对这些应用程序的直接 db 访问,并努力通过(希望是 RESTful)Web 服务为它们提供服务,以便您可以在业务逻辑级别管理数据完整性。

            如果第三方(内部或其他)应用程序对您的架构具有 DDL 访问权限,那么请不要再担心这个问题 - 您已经失去了对数据的控制权,而且您搞砸了!

            【讨论】:

              【解决方案9】:

              我个人的偏好是在数据库层强制执行基本验证,然后使用自省技术将这些约束作为默认值提升到应用程序层(约定优于配置)。如果我在表单或类似的表单中有一些不寻常的用户交互,那么我将用我需要的任何新行为覆盖我从数据库获得的默认值。这有助于我在数据库层保持最基本的验证,主要是 DRY,而我在应用程序层进行更复杂的验证(即电话号码格式)。

              我可以对电话号码之类的内容进行检查约束,但我发现检查约束是一种困难且令人沮丧的方式来强制执行格式。

              如果我有一个需要从第二个来源接收输入的应用程序(即它同时具有 Web 界面和桌面客户端),那么我会尝试让第二个界面通过第一个界面工作(通过例如 web 服务)或者如果失败了,那么除了在应用程序层进行验证之外,我还会在数据库中使用检查约束。

              首先,我希望尽可能地保持数据的完整性,因为不良数据将不可避免地对应用程序造成严重破坏——此外(实际上同样重要)我希望应用程序能够处理用户提供的信息。

              【讨论】:

                【解决方案10】:

                从 OOP 的角度来看,数据库就像 Object/Actor 一样,在更大的系统中,它应该对自己负责。这包括验证输入的必要需求。

                【讨论】:

                  【解决方案11】:

                  两者都是。我在最后一个地方发现了这个。我们有带有 sybase 数据库的旧式 delphi 系统。新系统是 .NET 和 Sql 服务器。一位特定的员工单独负责将 sybase 数据库转换为 sql server 数据库,以供需要升级到新 .NET 系统的客户使用。他从未使用过 .NET 应用程序代码,因此从未在应用程序级别看到数据约束。

                  因此,他不得不依赖传递给他的信息和数据库级别的数据约束。如果约束在数据库级别不正确或缺失,则会导致数据不正确并来自客户的支持电话。这种情况发生的次数超出了我们的预期,因为数据约束并不总是从应用程序级别复制到数据库级别。

                  【讨论】:

                    【解决方案12】:

                    最好在数据库级别强制执行这些约束,因为它们的性能更高(在 C 中实现)。可以在另一层上复制验证,因为它提供了更多用户友好的错误和验证消息。

                    这不是 XOR 命题,最好在安全方面犯错,对数据库执行这些约束将使您的系统更加durable。

                    【讨论】:

                    • 对您的回答的第一个陈述的投诉。我同意您的结论,但不同意您的推理:是什么让您认为数据库级约束是“用 C 实现的”,因此“性能更高”?比什么更高效?如果在 C 中也实现了应用程序级约束怎么办?请注意,该问题并不特定于任何具体的 RDBMS(即没有 DB2、Oracle 或 SQL Server 标记),并且没有规定所有 RDBMS 都必须用 C 编码。
                    【解决方案13】:

                    视情况而定。

                    如果您要构建的数据库要嵌入到您正在构建的单个应用程序中,您可以选择将数据验证放入 DBMS 或应用程序或两者中。几乎所有由数据库新手、经验丰富的程序员构建的数据库都属于这一类。在这种情况下,其他一些回复会回答您的问题。

                    但是,如果您正在构建一个旨在存储从多个应用程序接收的数据的数据库,并且其中一些应用程序的编程超出您的控制范围,那么您必须将 DBMS 构建为防御性的,并且不允许来自损坏的应用程序的错误数据会感染您提供给其他应用程序和交互式用户的数据。你无法捕捉到所有错误,但你可以捕捉到很多错误。

                    至少,您应该设计您的表,使它们至少有一个可能的主键(可能的主键称为候选键)。您应该从候选键中选择一个主键,并将其声明为主键约束。这会强制实体完整性。

                    此外,您应该为每个外键声明一个“引用”约束。这会强制执行参照完整性。使用一个不错的 DBMS,即使外键是可选的,也应该能够强制引用完整性,换句话说,它可能是 NULL。 NULL 当然不指代任何东西。

                    将这两种验证转移到应用程序是没有意义的。无论如何,该应用程序必须往返于数据库以检测违反规则的情况。

                    在我看来,业务逻辑不应该在数据库中的想法是对数据库的全部含义的误解。同样,如果您的数据库嵌入在单个应用程序中,那么适合您自己。

                    关于禁止缺失值 (NULLS) 的验证规则,在应用程序和数据库中实现它们并没有什么坏处。在许多情况下,这是正确的做法。与范围检查类似。

                    对于非常大的项目,您需要一个单独的文档来概述数据的所有业务规则。该文档应说明规则在何处执行,在数据库中、在应用程序中或两者中。

                    【讨论】:

                      【解决方案14】:

                      同样,在两个级别。即使是三点。

                      数据库级别的验证是长期数据完整性所必需的,而且只是为了安全。但是在发送到服务器之前,我们需要额外验证客户端表单中的数据。

                      我们可以使用 DB 中的约束来验证所有内容,但验证错误和警告消息必须本地化并以用户为中心,而不是以 API 为中心(带有错误代码)。

                      “拒绝访问”而不是“找不到记录”也可以解释更多。

                      所有三个级别都需要进行细微不同的验证。客户端、API 和数据库: - 客户端,因为它对于出色的用户体验和减少服务器负载是必要的。 - DB 是为了保持数据受限。 - API(服务器端)验证是为了让 API 用户能够区分消息。

                      【讨论】:

                      • 客户端的验证并不安全,因为您发送给客户端的任何数据都可能被泄露,包括验证码。您需要在没有人可以更改流程的地方验证输入。也有关于数据库错误本地化的解决方案。
                      • 当然客户端的验证是不安全的!他们应该只协助用户体验。验证应该在双方进行,并且客户端允许使大多数错误消息易于用户理解并支持本地化。如果您是 API 用户,最好在 DB 之前支持 API 级别的验证和本地化消息。在保持数据库完整性方面,有必要支持基于数据库的验证。因此,我们需要所有 3 个级别,开发人员根据目的进行权衡。对于拥有 10 个用户的简单 MVP,无论如何,客户端验证至关重要。
                      【解决方案15】:

                      我同意这个问题的答案取决于环境。

                      在我当前的环境中,我们的应用程序只有两个开发人员和不到一千名用户。如果您可以确保您的编程实践包括在应用程序层中实现业务逻辑的要求,那么您就可以成功地处理数据库之外的约束。

                      如果您的应用程序需要很好地扩展并且最终将由大量开发人员维护并被无数用户使用,那么可能在数据库中实施数据约束可以提高效率并消除潜在的灾难应用层的变化。

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2021-09-11
                        • 2011-01-16
                        • 2011-11-18
                        • 1970-01-01
                        • 2017-02-26
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-03-20
                        相关资源
                        最近更新 更多