我应该保护我的数据库不受无效数据的影响吗



我总是倾向于通过服务层"保护"我的持久层不受侵犯。然而,我开始怀疑这是否真的有必要。花点时间让我的数据库变得健壮,建立关系有什么意义;当数据完整性从未真正发挥作用时。

例如,考虑在Email字段上具有唯一约束的User表。我很自然地想在我的服务层中编写阻止程序代码,以确保在尝试添加任何内容之前,添加的电子邮件不在数据库中。然而,在过去,我从未真正看到它有什么问题,因为我接触到了更多的&更多最佳实践/设计原则我觉得这种方法不是很枯燥。

那么,总是确保进入持久层的数据确实"有效"是正确的吗?还是让无效数据进入数据库并处理错误更自然?

请不要这样做。

在并发环境中,即使实现诸如键之类的"简单"约束也绝对不是一件小事。例如,仅在第一步返回空结果的情况下,在一个步骤中查询数据库并允许在另一步中插入是不够的——如果并发事务在步骤一和步骤二之间插入了与您试图插入(并提交)的值相同的值,该怎么办?您有一个竞争条件,这可能导致重复的数据。对此,最简单的解决方案可能是使用全局锁来序列化事务,但随后可伸缩性就消失了。。。

对于键上的INSERT/UPDATE/DELETE操作的其他组合,以及其他类型的约束,如外键,在某些情况下甚至CHECK,也存在类似的考虑因素。

几十年来,DBMS设计了非常的聪明方法,在这种情况下既正确又高效,同时允许您以声明性的方式轻松定义约束,最大限度地减少出错的机会。所有访问同一数据库的应用程序都将自动受益于这些集中的约束。

如果你绝对必须选择哪一层代码不应该验证数据,那么数据库应该是你最后的选择。

那么,总是确保进入持久层的数据确实"有效"(服务层)是正确的吗?还是让无效数据进入数据库并处理错误更自然?

永远不要假设数据是正确的,尽可能多地在数据库级别进行验证。

是否在代码的上层验证取决于具体情况,但在密钥冲突的情况下,我会让数据库来承担重任。

尽管没有一个决定性的答案,但我认为这是一个很好的问题。

首先,我强烈支持在数据库中至少包含基本验证,并让数据库做它擅长的事情。至少,这意味着外键、适当的NOT NULL、尽可能强类型的字段(例如,不要将文本字段放在整数所属的位置)、唯一约束等。让数据库处理并发性也是至关重要的(正如@Branko Dimitrijevic所指出的),事务原子性应该归数据库所有。

如果这是适度多余的,那就顺其自然。验证过多总比验证过少好。

然而,我认为,即使逻辑存在于数据库中,业务层也应该意识到它正在执行的验证。

  • 区分异常和验证错误可能更容易。在大多数语言中,失败的数据操作可能会表现为某种异常。大多数人(包括我在内)都认为,在常规程序流中使用异常是不好的,我认为电子邮件验证失败(例如)不是"异常"情况。

    更荒谬的是,想象一下,访问数据库只是为了确定用户是否已经填写了表单上的所有必填字段

    换句话说,我宁愿调用一个方法IsEmailValid()并接收一个布尔值,也不愿试图确定抛出的数据库错误是否意味着电子邮件已经被其他人使用。

    这种方法也可以执行得更好,并避免诸如由于INSERT失败而跳过ID之类的麻烦(从SQL Server的角度来看)。

    如果验证电子邮件的逻辑比简单的唯一约束更复杂,那么它很可能存在于可重复使用的存储过程中。

    最终,在业务层出错的情况下,这个简单的唯一约束提供了最终保护。

  • 一些验证根本不需要进行数据库调用就可以成功,即使数据库可以很容易地处理它

  • 有些验证比单独使用数据库结构/函数来表达更为复杂。

  • 即使对于相同(完全有效)的数据,应用程序之间的业务规则也可能不同。

  • 有些验证是如此关键或昂贵,以至于它应该在数据访问之前进行。

  • 一些简单的约束,如字段类型/长度,可以实现自动化(通过ORM运行的任何东西都可能具有一定程度的自动化)。

这样做的两个原因。可以从另一个应用程序访问数据库。。

你可能会在代码中犯一个小错误,并将数据放在数据库中,因为你的服务层是在假设这种情况永远不会发生的情况下运行的,所以如果幸运的话,它会崩溃,无声的数据损坏是最糟糕的情况。

当我在代码中犯错误时,我总是把DB中的规则视为极少数情况下的后盾。:)

需要记住的是,如果需要的话,你可以随时放松限制,在用户花费大量精力输入数据后收紧限制会带来更大的问题。

要真正小心这个词,在IT中,它的意思比你希望的要早得多。

最新更新