我正在研究数据库,并且关注关系型数据库的一些局限性。
我了解到,连接大型表格是非常昂贵的,但我不完全清楚其中的原因。DBMS需要执行什么操作来执行连接操作?哪里是瓶颈?
反规范化如何帮助克服这种费用?其他优化技术(例如索引)如何帮助?
欢迎分享个人经验!如果您要发布资源链接,请避免使用维基百科。我已知道该去哪里找。
关于这一点,我对BigTable和SimpleDB等云服务数据库使用的反规范化方法很感兴趣。请参见此问题。
我正在研究数据库,并且关注关系型数据库的一些局限性。
我了解到,连接大型表格是非常昂贵的,但我不完全清楚其中的原因。DBMS需要执行什么操作来执行连接操作?哪里是瓶颈?
反规范化如何帮助克服这种费用?其他优化技术(例如索引)如何帮助?
欢迎分享个人经验!如果您要发布资源链接,请避免使用维基百科。我已知道该去哪里找。
关于这一点,我对BigTable和SimpleDB等云服务数据库使用的反规范化方法很感兴趣。请参见此问题。
反规范化以提高性能?听起来很有说服力,但事实并非如此。
克里斯·戴特(Chris Date)与特德·科德博士一起成为关系数据模型的最初倡导者,在对抗反规范化的错误论证上耐心耗尽后,他运用科学方法进行了系统的破坏:他获得了大型数据库并测试了这些主张。
我认为他在《关系数据库写作1988-1991》中写了这个内容,但这本书后来被编入第六版的《数据库系统概论》中,后者是关于数据库理论和设计的权威文本,在我写作本文时已经是第八版,并有可能保持出版数十年。当我们中的大多数人还赤脚奔跑时,克里斯·戴特就是这个领域的专家。
他发现:
这一切归结为减少工作集大小。涉及正确选择的键和正确设置索引的连接是便宜的,而不是昂贵的,因为它们允许在行材料化之前对结果进行大量修剪。
将结果材料化涉及批量磁盘读取,这是整个过程中最昂贵的方面。相比之下,执行连接逻辑上仅需要检索关键字。实际上,甚至不会提取关键值:键哈希值用于连接比较,缓解了多列连接的成本,并极大地减少了涉及字符串比较的连接成本。不仅更多的内容可以适应高速缓存,还可以减少磁盘读取量。
此外,优秀的优化器将选择最严格的条件并在执行连接之前应用它,非常有效地利用基数高的索引上连接的高选择性。
诚然,这种类型的优化也可以应用于反规范化的数据库,但通常想要反规范化模式的人在设置索引时(如果有的话)通常不考虑基数问题。
重要的是要理解,在实践中,表扫描(在生成连接期间检查表中每一行)很少出现。只有在以下情况下,查询优化器才会选择表扫描:
SELECT * FROM A,B
注释:
David Aldridge 提供了一些重要的额外信息。
除了索引和表扫描之外,确实有各种其他策略,并且现代优化器将在生成执行计划之前对它们进行成本估算。
一个实用的建议:如果可以用作外键,则对其进行索引,以便优化器可用索引策略。
我曾经比 MSSQL 优化器聪明。两个版本之前就改变了。现在通常是它在教导我。它在非常真实的意义上是一种专家系统,将许多非常聪明的人的所有智慧在一个足够封闭的领域进行编码,以便规则为基础的系统有效。“胡说八道”可能有些不得体。我被要求少一点傲慢,并且被提醒数学并不会说谎。这是正确的,但并不是数学模型的所有含义都应该被字面解释。如果你小心避免检查它们的荒谬性(双关语),并确保在解释方程之前将它们全部消除,那么负数的平方根非常有用。
我之所以如此激烈地回应是因为该语句所述的内容是:“连接是笛卡尔积……”这可能不是作者原意,但这就是写下来的,而且这是明显错误的。笛卡尔积是一个关系,连接是一个函数,更具体地说,连接是一个产生关系值的函数。使用空谓词,它将产生笛卡尔积,并检查它是否确实如此是数据库查询引擎的一项正确性检查。但是在实践中没有人编写无限制的连接,因为除了教室外,它们没有任何实际价值。
我指出这一点是因为我不希望读者陷入将模型与被建模事物混淆的古老陷阱中。模型是一种近似,专门简化以便于方便操作。
表扫描连接策略的选择截止时间可能因数据库引擎而异。它受多种实现决策的影响,例如树节点填充因子、键值大小和算法的微妙之处,但总体而言,高性能索引的执行时间为k log n + c。 C项是固定的开销,主要由设置时间组成,曲线的形状意味着与线性搜索相比,直到n达到几百时才能获得回报。
有时候非规范化是一个好主意。非规范化是对特定连接策略的一项承诺。正如前面提到的,这会干扰其他连接策略。但如果你有大量的磁盘空间、可预测的访问模式,并且倾向于处理大部分或全部数据,则预先计算连接可以非常有价值。
您还可以确定操作通常使用的访问路径并预先计算所有这些访问路径的连接。这是数据仓库背后的原理,至少在那些由知道自己在做什么的人构建的数据仓库中是这样的,而不是为了符合流行语的要求而构建的。一个正确设计的数据仓库通过定期将其从归一化的交易处理系统转换出来而产生。这种操作和报告数据库的分离具有非常理想的效果,可以消除OLTP和OLAP(在线事务处理即数据输入以及在线分析处理即报告)之间的冲突。
这里一个重要的问题是,除了定期更新之外,数据仓库是只读的。这使得更新异常的问题无意义。请勿将您的OLTP数据库(数据输入发生的数据库)去规范化。这可能会加快账单运行速度,但如果这样做,您将会遇到更新异常。曾经试过让《读者文摘》停止给你发送杂志吗?
如今,磁盘空间很便宜,所以尽管放心使用。但规范化只是数据仓库中的一部分。更大的性能提升来自于预计算的滚动值:例如月度总数等。这永远都是关于减少工作集。
ADO.NET类型不匹配问题
假设您有一个包含索引列类型为varchar的SQL Server表,并使用AddWithValue传递参数约束对此列进行查询。C#字符串是Unicode,因此推断的参数类型将为NVARCHAR,而不是VARCHAR。
VARCHAR到NVARCHAR是一种扩展转换,因此它会隐式发生 - 但是说再见索引,并祝你好运弄清原因。
"计算磁盘访问次数"(Rick James)
如果所有内容都缓存在RAM中,JOIN操作相当便宜。也就是说,规范化并没有太多性能惩罚。
如果“规范化”模式导致JOIN操作经常访问磁盘,而等效的“去规范化”模式不需要访问磁盘,那么去规范化会在性能竞争中获胜。
原作者的评论:现代数据库引擎非常擅长组织访问序列以最小化联接操作期间的缓存未命中。上面这一点虽然正确,但可能被曲解为联接对大型数据必然具有问题。这将导致经验不足的开发人员做出错误决策。
大多数评论者未注意到在复杂关系型数据库中可用的广泛连接方法,而非规范化者总是忽略了维护非规范化数据的更高成本。并非每个连接都基于索引,并且数据库具有许多优化算法和连接方法,旨在降低连接成本。
无论如何,连接的成本取决于其类型和其他几个因素。它可能根本不昂贵-以下是一些示例。
数据库就是为了连接而设计的,它们在如何进行连接方面非常灵活,并且通常表现出色,除非连接机制出错。
我认为整个问题的前提是错误的。在大型表上进行联接并不一定代价高昂。事实上,高效进行联接是关系型数据库存在的主要原因之一。对大型集合进行联接通常是昂贵的,但你很少想要将大表 A 的全部内容与大表 B 的全部内容进行联接。相反,您编写查询时应只使用每个表的重要行,使得通过联接实际保留的集合更小。
此外,正如 Peter Wone 所提到的,您可以提高效率,这样只有每个记录的重要部分需要在内存中,直到最终结果集被实现。此外,在具有许多联接的大型查询中,您通常希望从较小的表集开始,并逐步向上处理较大的表集,以便在尽可能长的时间内保留在内存中的集合尽可能小。
如果正确执行,联接通常是比较、合并或过滤大量数据的最佳方法。
瓶颈基本上总是磁盘I/O,更具体地说是随机磁盘I/O(相比之下,顺序读取相当快,并且可以使用预读取策略进行缓存)。
如果您跳转并读取大表的小部分,则连接可能会增加随机查找。但是,查询优化器会寻找那些内容并将其转换为顺序表扫描(丢弃不需要的行),如果认为那样更好。
单个非规范化表存在类似的问题 - 行很大,因此较少适合单个数据页。如果您需要位于另一个位置远的行(且由于大行大小而使它们更分散),则您将有更多的随机I/O。同样,可能会被强制执行表扫描以避免这种情况。但是,这一次,由于行大小较大,您的表扫描必须读取更多的数据。再加上您正在从单个位置复制数据到多个位置,RDBMS必须读取(和缓存)更多数据。
使用2个表,您还可以获得2个聚集索引,并且通常可以索引更多内容(由于更少的插入/更新开销),这可以极大地提高性能(主要是因为索引是(相对)小的,从磁盘快速读取(或便宜缓存),并减少您需要从磁盘中读取的表行数)。
关于连接的唯一开销来自于找到匹配行。 Sql Server使用3种不同类型的连接,主要基于数据集大小,以查找匹配行。如果优化器选择了错误的连接类型(由于不准确的统计信息,不充分的索引或仅是优化器错误或边缘情况),它可能会极大地影响查询时间。
在最理想的情况下,它们不会导致磁盘I/O,因此从性能角度来看可以忽略不计。
总的来说,最坏的情况是,从x个联接表中读取相同数量的逻辑数据与从单个非规范化表中读取相同数量的逻辑数据速度应该是相同的,因为磁盘读取更小。要读取相同数量的物理数据,则可能会有一些轻微的开销。
由于查询时间通常由I/O成本主导,并且通过非规范化使数据大小不发生变化(减去一些非常微小的行开销),所以仅仅将表合并在一起并不能带来太大的好处。根据我的经验,倾向于提高性能的非规范化类型是缓存计算值而不是读取需要计算这些值所需的10,000行。
你连接表的顺序非常重要。如果你有两个数据集,请尽量以最小的数据集优先构建查询,以减少查询需要处理的数据量。
对于某些数据库来说,这并不重要,例如MS SQL大多数情况下都知道正确的连接顺序。 但对于一些数据库(如IBM Informix),连接顺序就很重要了。
在考虑连接的复杂度类时,决定是否进行反规范化或规范化是一个相当简单的过程。例如,当查询为O(k log n)(其中k与所需输出量相关)时,我倾向于使用规范化来设计我的数据库。
一种简单的反规范化和优化性能的方法是考虑如何更改规范化结构会影响到反规范化结构。然而,这可能需要事务逻辑来处理反规范化结构,因此可能会出现问题。
规范化和反规范化的争论不会结束,因为问题很多。有许多问题需要同时采用这两种方法才能得到自然解决方案。
作为一般规则,我总是存储规范化结构和可以重建的反规范化缓存。最终,这些缓存可以帮助我解决未来的规范化问题。
深入探讨他人所说的话,
连接就是一些带有唇彩的笛卡尔积。 {1,2,3,4}X{1,2,3} 将给我们12个组合(nXn=n^2)。这个计算出的集合作为一个参考,在其上应用条件(例如左右两边都是2或3),数据库管理系统将条件应用于给出与之匹配的条件。实际上,它更加优化,但问题是相同的。集合大小的变化会以指数形式增加结果大小。所有消耗的内存和CPU周期都受到指数级影响。
当我们非规范化时,我们完全避免了这种计算,想象一下每一页书上都贴着有颜色的便签,您可以在不使用参考的情况下推断信息。我们付出的代价是牺牲了DBMS的本质(数据的最佳组织)。