请对不规范化的情况提出您的意见。我刚刚目睹了一位架构师和 DBA 之间的激烈讨论,他们坚持认为其中一个人在争论数据库过于规范化。
10 回答
规则是规范化直到它受伤,然后非规范化直到它起作用。(谁说的?)
一般来说,当我有很多父子关系时,我经常去规范化,我知道我经常需要加入五六个大表来获取一条数据(例如客户端 ID),并且不需要任何大部分时间来自中间表的信息。如果可能的话,我会尝试对不会经常更改的事物(例如 id 字段)进行非规范化。但是,无论何时进行非规范化,都必须编写触发器或其他一些过程(但如果不能通过 PK/FK 关系和级联更新来处理,通常会触发)以确保数据保持同步。如果您未能在数据库级别执行此操作,那么您将遇到数据完整性问题并且您的数据将变得无用。不要认为您可以通过应用程序代码维护非规范化。这是灾难的秘诀,
正确的非规范化会减慢插入、更新和删除的速度,尤其是在您需要处理大量数据时。它可能会也可能不会提高选择查询速度,具体取决于您需要如何查询数据。如果您最终需要进行大量自联接来获取数据,那么您最好不要进行非规范化。永远不要在没有测试的情况下进行反规范化,看看你是否提高了性能。请记住,当许多用户使用它时,减慢插入/更新/删除速度将对系统产生整体影响。通过非规范化来解决一个问题,您可能会在整个系统中引入一个更严重的问题。不要只测试你想要加速的一个查询,还要测试整个系统的性能。您可能会加快每月运行一次的查询,并减慢每天运行数千次的其他查询。
非规范化通常用于数据仓库,这是一种特殊情况,因为它们通常按计划自动更新,而不是由用户一次更新一条记录。专门从事数据仓库的 DBA 也倾向于构建它们,他们知道如何避免数据完整性问题。
另一种常见的非规范化技术是为与不需要与实时数据一起运行的复杂报告相关的数据创建临时表。这是一种穷人的数据仓库,如果没有按计划更新登台表的方法,永远不应该这样做(尽可能少见,这使用的服务器资源可以更好地花在大部分时间其他地方。 ) 通常这些类型的表会在系统上的用户很少并且比实时数据滞后一整天时更新。不要考虑这样做,除非您为其暂存数据的查询确实很慢并且无法以其他方式进行优化。许多慢查询可以在不进行反特征化的情况下进行优化,因为开发人员通常使用最容易理解而不是最高效的方式来选择数据。
执行查询的可能性:如果数据库过于规范化,这可能会导致查询中出现大量连接,并限制您搜索特定属性的可能性。在进行数据库设计时,您应该通过访问路径分析来考虑您计划使用它的方式。
详细地说,规范化频繁更新的数据和非规范化大部分读取的数据是一个经验法则。
当您过早地进行优化时
一些正常化是为了允许未来的增长,你可能不需要它。
例如,假设您有一张person
桌子。你可以有birthday
一个列,因为每个人只有一个生日。
如果您严格规范化,则不会将phone_number
and用作cell_number
中fax_number
的列person
,而是可能有一个phonenumber
表,其中每行都有一个数字、一个类型和一个 person_id 关系。这比在person
表中粘贴新列要好,因为
- 许多人不会拥有所有这些,留下很多空白,并且
- 如果某人有 3 个单元格号码,您将添加尴尬的列,例如
cell_number_2
问题 1 是有效的,但问题 2 可能是“你不需要它”的一个例子。可以说“我们只允许一个单元格编号,仅此而已。” 在这种情况下,您可能不会费心为电话号码制作单独的表格。
这是一个权衡。通过不制作单独的表,您并没有严格规范化,并且您可能有很多 NULL 空间。但是您要做的连接也更少,而且工作量也更少。
像许多好的做法一样,规范化本身可以成为目的——你私下给自己的金徽章,因为你做对了。这很好。但是很高兴意识到有时可以为了保持简单而改变规则。
最后一件事:您必须权衡一个事实,即一旦您的代码启动并运行,更改您的数据库架构是很糟糕的。所以说“我们不需要它”是可以的,但在你提交之前试着确定一下。
关于存储与性能的几个很好的答案,但除此之外,我还要补充一点,对我来说,应该考虑去规范化的另一个迹象是,你将需要使用自联接进行查询。
从概念上讲,自联接表当然没有任何问题,但经验表明,对于没有经验的程序员来说,它是更难掌握的概念之一,因此往往会产生错误。如果您可以设计出这些需求,您很可能会简化未来的维护路径。
这当然是一个判断问题,也是一个指示,而不是规则。
您必须找到最佳位置...过于规范化,您最终会得到许多“臃肿”的抽象结构,其中仅包含 1 或 2 列数据,并且您最终会为大多数查询加入 5 个表。
由于标准化不足,您最终会在很多不同的地方拥有大量数据。由于缓存大小等原因,这可能会导致数据库变慢。此外,现在当您需要更新某些内容时,您有 4 个不同的表要更新,而不是 1 个,甚至不要让我开始确保不同位置的所有数据都匹配!
基本上,选择你的毒药,观察你的数据库将如何使用,并保持清醒。所有规则都是为了被打破,如果你在可能 2 个非常常访问的地方有一段数据,我会说没关系。这是一个关键部分,其中加入(可能超过 2 个)表可能太昂贵了。但也不要对数据库的空间或速度进行微优化。
在他们过于正常化的地方工作。他们从邮寄地址表中删除了“状态”列。他们放置了一个链接到状态表的整数外键字段来代替 2 字节的状态列。
总之:
他们用地址表中的 4 字节列替换了 2 字节状态列。现在每行需要多 2 个字节的存储空间。
他们添加了一个包含 4 字节主键列和 2 字节状态列的状态表。占用更多空间来存储这张表。
数据库保留状态表中键的 btree 索引。占用更多空间。
检索地址的 sql 更难编写。
检索地址的 sql 比原来的要慢。
当然,这天真地删除了重复的不变数据。结果是它使用更多的磁盘空间,更难使用,使用更慢。
你绝对可以正常化太多。
规范化消除了冗余,但如果它以某种方式降低性能(由于所有必需的连接),则硬件成本不足,是时候为性能考虑允许冗余了。这是我的经验法则。在回答时间长的情况下也是如此。
出于性能原因,数据仓库通常使用非规范化方法。根据维基百科:
一个标准的数据库设计指南是设计者应该创建一个完全规范化的设计;出于性能原因,可以随后执行选择性非规范化。然而,一些建模学科,例如数据仓库设计的维度建模方法,明确推荐非规范化设计,即在很大程度上不遵守 3NF 的设计。
报告和数据仓库可能是您发现数据非规范化的最大场所。OLAP系统通常总是将非规范化为单个表或一组表。
当您遵循导致未完全规范化的架构的设计规范时,请勿进行规范化。一个这样的设计学科是星型模式设计,它的一个紧密变体是雪花模式。
星型和雪花型都将生成一个更易于用于各种报告、自定义提取以及与 OLAP 工具(如 Cognos Power Play)的接口的模式。不足之处?在插入/更新/删除数据时,每次偏离其中一种正常形式(1NF 除外)都会带来异常。如果你真的知道正常形式,你就会知道相关的异常是什么。当您编写 ETL(提取、转换和加载)程序以保持您的星/雪花处于最新状态时,您必须解决这些异常。
那么什么时候星型或雪花型模式比规范化模式更好?通常,用于数据仓库、数据集市和报告数据库。在我自己的实践中,我从来没有构建过不是 OLTP 数据库后端的其中一个,并且 OLPT 数据库受益于几乎完全的规范化。不要只是非规范化和放弃所有纪律。这就像随机设计。