非规范化以提高性能?听起来很有说服力,但站不住脚。
Chris Date 与 Ted Codd 博士一起是关系数据模型的最初支持者,他对反对标准化的错误论点失去了耐心,并使用科学方法系统地推翻了它们:他拥有大型数据库和tested这些断言。
我认为他写在1988-1991 年关系数据库著作但这本书后来被编入了第六版数据库系统简介,即the关于数据库理论和设计的权威文本,在我撰写时已是第八版,并且可能在未来几十年内继续印刷。当我们大多数人还赤脚奔跑时,克里斯·戴特(Chris Date)就是这个领域的专家。
他发现:
- 其中一些适用于特殊情况
- 所有这些都无法满足一般用途
- 对于其他特殊情况,所有这些都明显更糟
这一切都归结于减小工作集的大小。涉及正确选择的键和正确设置的索引的连接是便宜的,而不是昂贵的,因为它们允许对结果进行显着的修剪before这些行被具体化了。
实现结果涉及批量磁盘读取,这是该练习中成本最高的一个数量级。相比之下,执行连接在逻辑上只需要检索keys。实际上,甚至不获取键值:键哈希值用于连接比较,减轻多列连接的成本,并从根本上降低涉及字符串比较的连接成本。不仅会更适合缓存,而且需要执行的磁盘读取也会少得多。
此外,一个好的优化器会选择最严格的条件并在执行连接之前应用它,非常有效地利用高基数索引上连接的高选择性。
诚然,这种类型的优化也可以应用于非规范化数据库,但是那些人want当(如果)他们设置索引时,非规范化模式通常不会考虑基数。
重要的是要理解表扫描(在生成连接的过程中检查表中的每一行)在实践中很少见。仅当满足以下一项或多项条件时,查询优化器才会选择表扫描。
- 关系中的行数少于 200(在这种情况下,扫描会更便宜)
- 连接列上没有合适的索引(如果连接这些列有意义,那么为什么它们没有索引?修复它)
- 在比较列之前需要进行类型强制(WTF?!修复它或回家)请参阅 ADO.NET 问题的末尾注释
- 比较的参数之一是表达式(无索引)
执行某项操作比不执行该操作的成本更高。然而,执行wrong操作,被迫进行无意义的磁盘 I/O,然后在执行您真正需要的连接之前丢弃糟粕,是much更贵。即使预先计算了“错误”的操作并且合理地应用了索引,仍然存在显着的惩罚。非规范化以预先计算连接(尽管会带来更新异常)是对特定连接的承诺。如果您需要一个不同的加入,这个承诺会让你付出代价big.
如果有人想提醒我这是一个不断变化的世界,我想你会发现更糟糕的硬件上更大的数据集只会夸大 Date 发现的传播范围。
对于所有从事计费系统或垃圾邮件生成器工作的人(为你们感到羞耻),并愤怒地把手放在键盘上告诉我,你们知道非规范化更快的事实,抱歉,但你们生活在一个特殊的环境中。案例 - 具体来说,您处理的案例all的数据,按顺序。这不是一般情况,而且你are你的策略是合理的。
You are not错误地概括它是合理的。有关在数据仓库场景中适当使用标准化的更多信息,请参阅注释部分的末尾。
我也想回复一下
连接只是带有一些唇彩的笛卡尔积
真是一堆废话。尽早实施限制,最严格的首先实施。你读过这个理论,但你还没有理解它。连接是treated作为“谓词适用的笛卡尔积”only由查询优化器。这是一种符号表示(实际上是标准化),以促进符号分解,以便优化器可以生成所有等效的转换,并按成本和选择性对它们进行排序,以便可以选择最佳查询计划。
让优化器生成笛卡尔积的唯一方法是不提供谓词:SELECT * FROM A,B
Notes
大卫·奥尔德里奇提供了一些重要的附加信息。
除了索引和表扫描之外,确实还有多种其他策略,现代优化器将在生成执行计划之前将它们全部消耗掉。
一个实用的建议:如果它可以用作外键,那么就对其进行索引,这样索引策略就是可用的给优化器。
我曾经比MSSQL优化器更聪明。两个版本前就改变了。现在一般都教me。从真正的意义上来说,它是一个专家系统,将许多非常聪明的人的所有智慧编入一个足够封闭的领域,使得基于规则的系统是有效的。
“胡说八道”可能有些不机智。他们要求我不要那么傲慢,并提醒我数学不会说谎。这是事实,但并非数学模型的所有含义都必须从字面上理解。如果你小心地避免检查它们的荒谬性(双关语),并确保在尝试解释方程之前将它们全部取消,那么负数的平方根会非常方便。
我之所以做出如此野蛮的回应,是因为该声明的措辞是这样的:
Joins are笛卡尔积...
这可能不是本意,但它is所写的内容,绝对是不真实的。笛卡尔积是一种关系。连接是一个函数。更具体地说,连接是关系值函数。使用空谓词,它将产生笛卡尔积,检查它是否这样做是对数据库查询引擎的正确性检查,但在实践中没有人编写无约束连接,因为它们在课堂之外没有实际价值。
我之所以这么说,是因为我不想让读者陷入将模型与建模对象混淆的古老陷阱。模型是一种近似值,是为了方便操作而故意简化的。
表扫描连接策略选择的截止点可能因数据库引擎而异。它受到许多实现决策的影响,例如树节点填充因子、键值大小和算法的微妙之处,但一般来说,高性能索引的执行时间为k log n + c。 C 项是固定开销,主要由设置时间组成,并且曲线的形状意味着您不会获得回报(与线性搜索相比),直到n有数百个。
有时非规范化是个好主意
非规范化是对特定连接策略的承诺。如前所述,这会干扰other加入策略。但是,如果您拥有大量磁盘空间、可预测的访问模式,并且倾向于处理大部分或全部磁盘空间,那么预先计算联接可能非常值得。
您还可以找出您的操作通常使用的访问路径,并预先计算这些访问路径的所有联接。这是数据仓库背后的前提,或者至少当它们是由那些知道为什么要做他们正在做的事情的人构建的,而不仅仅是为了遵守流行语时。
正确设计的数据仓库是通过标准化事务处理系统的批量转换定期生成的。操作和报告数据库的这种分离具有消除OLTP和OLAP(在线事务处理,即数据输入和在线分析处理,即报告)之间的冲突的非常理想的效果。
这里重要的一点是,除了定期更新之外,数据仓库还只读。这使得更新异常的问题变得毫无意义。
不要错误地对 OLTP 数据库(发生数据输入的数据库)进行非规范化。计费运行可能会更快,但如果这样做,您将收到更新异常。曾经尝试过让《读者文摘》停止向您发送内容吗?
如今磁盘空间很便宜,因此请淘汰自己。但非规范化只是数据仓库故事的一部分。更大的性能提升来自于预先计算的汇总值:每月总计,诸如此类的事情。它是always关于减少工作集。
ADO.NET 类型不匹配问题
假设您有一个包含 varchar 类型索引列的 SQL Server 表,并且您使用 AddWithValue 传递一个参数来限制对此列的查询。 C# 字符串是 Unicode,因此推断的参数类型将为 NVARCHAR,这与 VARCHAR 不匹配。
VARCHAR 到 NVARCHAR 是一种扩大的转换,因此它是隐式发生的 - 但请告别索引,祝你好运找出原因。
“计算磁盘点击次数”(里克·詹姆斯)
如果所有内容都缓存在 RAM 中,JOINs
相当便宜。也就是说,归一化没有太多作用绩效惩罚.
如果“规范化”模式导致JOINs
大量访问磁盘,但等效的“非规范化”模式不必访问磁盘,那么非规范化就赢得了性能竞争。
原作者的评论:现代数据库引擎非常擅长组织访问顺序,以最大限度地减少连接操作期间的缓存未命中。上述内容虽然正确,但可能会被误解为暗示连接在大数据上必然会带来昂贵的问题。这将导致缺乏经验的开发人员做出糟糕的决策。