去规范化以提高性能?听起来很令人信服,但它没有水。
与Ted Codd博士一起工作的Chris Date是关系数据模型的最初拥护者,他忍受了对归一化的误导性论证,并用科学的方法对其进行了系统地拆除:他拥有大型数据库并测试了这些断言。
我认为他是在1988-1991年间的关系数据库著作中写的,但是后来被本书卷入《数据库系统简介》第六版,这是有关数据库理论和设计的权威文章,在我撰写本书时是第八版,并且很可能会保留下来。几十年来一直在印刷。当我们大多数人仍赤脚奔跑时,克里斯·达特曾是该领域的专家。
他发现:
- 其中一些适合特殊情况
- 他们都无法偿还一般用途
- 对于其他特殊情况,所有这些都明显更糟
一切都归结为减轻工作集的规模。包含正确选择的索引和正确设置的索引的连接便宜而又不昂贵,因为它们可以在实现行之前对结果进行大量修剪。
实现结果涉及批量磁盘读取,这是该练习中最昂贵的一个数量级。相比之下,执行联接在逻辑上仅需要检索键。实际上,甚至不获取键值:键哈希值用于连接比较,从而减轻了多列连接的成本,并从根本上降低了涉及字符串比较的连接的成本。不仅更适合缓存,而且磁盘读取的工作量也要少得多。
而且,一个好的优化器将选择限制性最强的条件并在执行联接之前应用它,非常有效地利用联接对具有高基数的索引的高选择性。
诚然,这种类型的优化也可以应用于非规范化的数据库,但是那种想要对规范进行非规范化的人通常在设置索引时就不会考虑基数。
重要的是要了解,表扫描(在生成联接的过程中检查表中的每一行)在实践中很少见。仅当以下一项或多项成立时,查询优化器才会选择表扫描。
- 关系中少于200行(在这种情况下,扫描会便宜一些)
- 连接列上没有合适的索引(如果在这些列上连接有意义,那么为什么不对它们建立索引呢?修复它)
- 列可以进行比较(WTF?修复它,要么回家)前的类型强制要求见底笔记ADO.NET发行
- 比较的参数之一是表达式(无索引)
执行一项操作要比不执行一项操作花费更多。但是,执行错误的操作(被迫进入无意义的磁盘I / O,然后在执行您真正需要的连接之前丢弃浮渣)要昂贵得多。即使预先计算了“错误”的操作并且明智地应用了索引,仍然存在很大的代价。尽管需要进行更新异常,但通过非规范化来预先计算联接是对特定联接的承诺。如果您需要其他联接,那么该承诺将使您付出巨大的代价。
如果有人想提醒我这是一个不断变化的世界,我想您会发现,gruntier硬件上的更大数据集只会夸大Date的发现之广。
对于所有使用计费系统或垃圾邮件生成器(感到羞耻)并且愤慨地动手写键盘的人,告诉我您知道反规范化速度更快的事实,对不起,您生活在一个特殊的群体中。案例-具体来说是按顺序处理所有数据的案例。这不是一般情况,您的策略是合理的。
您没有理由对它进行错误的概括。有关在数据仓库场景中适当使用非规范化的更多信息,请参见注释部分的结尾。
我也想回应
联接只是具有一些唇彩的笛卡尔积
多么胡扯。限制要尽早应用,首先要严格限制。您已阅读该理论,但尚未理解。仅查询优化器将联接视为 “谓词适用的笛卡尔积” 。这是一种符号表示形式(实际上是一种归一化),有助于符号分解,因此优化程序可以生成所有等效转换,并按成本和选择性对它们进行排名,以便可以选择最佳查询计划。
获得生产笛卡尔乘积的优化器的唯一方法是不提供谓词: SELECT * FROM A,B
笔记
David Aldridge提供了一些重要的附加信息。
实际上,除了索引和表扫描之外,还有许多其他策略,而现代的优化器将在制定执行计划之前将它们全部花费。
一条实用的建议:如果可以将其用作外键,则对其进行索引,以便优化器可以使用索引策略。
我曾经比MSSQL优化器更聪明。那在两个版本之前改变了。现在它一般会教给我。实际上,它是一个专家系统,可以将许多非常聪明的人的所有智慧汇集到一个足够封闭的领域中,从而使基于规则的系统有效。
“ Blocks”可能没有任何技巧。我被要求不要那么傲慢,并提醒我数学不是在撒谎。的确如此,但数学模型的所有含义不一定都应从字面上理解。如果您仔细地避免检查它们的荒谬性(双关),并确保在尝试解释方程式之前先将其全部抵消,则负数的平方根非常有用。
我之所以如此大胆地回应的原因是,措词如下:
联接是笛卡尔积...
这可能不是什么意思,但是它是写的,而且绝对是错误的。笛卡尔积是一个关系。联接是一种功能。更具体地说,联接是一种关系值函数。如果谓词为空,则将生成笛卡尔乘积,并且对其进行检查是对数据库查询引擎的一次正确性检查,但实际上没有人编写不受约束的联接,因为它们在课堂外没有任何实用价值。
我之所以这样称呼,是因为我不希望读者陷入将模型与建模对象混淆的古老陷阱。模型是一个近似值,为方便操作而特意简化。
在不同数据库引擎之间,选择表扫描联接策略的临界值可能会有所不同。它受许多实现决策的影响,例如,树节点填充因子,键值大小和算法的精妙之处,但从广义上讲,高性能索引的执行时间为k log n + c。C项是固定的开销,主要由建立时间组成,曲线的形状意味着直到n达到数百时,您才能获得回报(与线性搜索相比)。
有时非正规化是个好主意
非规范化是对特定联接策略的承诺。如前所述,这会干扰其他联接策略。但是,如果您拥有大量的磁盘空间,可预测的访问模式以及倾向于处理很多或全部磁盘的趋势,那么预先计算联接可能非常值得。
您还可以找出您的操作通常使用的访问路径,并预先计算这些访问路径的所有联接。这是数据仓库背后的前提,或者至少是在由知道自己为什么要做自己的事情的人建造它们的时候,而不仅仅是为了遵守流行语。
经过适当设计的数据仓库会通过从规范化交易处理系统中进行批量转换而定期生成。操作和报告数据库的这种分离具有消除OLTP和OLAP(在线事务处理即数据输入和在线分析处理即报告)之间的冲突的非常理想的效果。
这里重要的一点是,除了定期更新之外,数据仓库是只读的。这就提出了更新异常的问题。
不要犯对OLTP数据库(进行数据输入的数据库)进行规范化的错误。计费运行可能会更快,但是如果这样做,则会出现更新异常。有没有试图让《读者文摘》停止向您发送东西?
这些天磁盘空间很便宜,所以请自行解决。但是非规范化只是数据仓库的一部分。预先计算的汇总值可带来更大的性能提升:每月总计,诸如此类。它总是约降低了工作组。
类型不匹配的ADO.NET问题
假设您有一个SQL Server表,其中包含varchar类型的索引列,并使用AddWithValue传递参数来约束对该列的查询。C#字符串是Unicode,因此推断的参数类型将是NVARCHAR,与VARCHAR不匹配。
从VARCHAR到NVARCHAR的转换是一个扩大的转换,因此它隐式发生-但是,告别索引,祝您好运。
“计算磁盘命中率”(Rick James)
如果所有内容都缓存在RAM中,那就JOINs
便宜了。也就是说,归一化不会带来太多的性能损失。
如果“规范化”模式导致JOINs
大量磁盘损坏,但是等效的“非规范化”模式则不必击中磁盘,那么非规范化将赢得性能竞争。
原始作者的评论:现代数据库引擎非常擅长组织访问排序,以最大程度地减少联接操作期间的缓存丢失。上面的虽然是正确的,但可能会被误解为暗示在大数据上连接必定会带来问题,代价是昂贵的。这将导致经验不足的开发人员做出错误的决策。