我正在做一些关于数据库的研究,我正在研究关系数据库的一些局限性。

我得到的大型表的连接是非常昂贵的,但我不完全确定为什么。DBMS需要做什么来执行连接操作,瓶颈在哪里? 非正规化如何有助于克服这一费用?其他优化技术(例如索引)有什么帮助?

欢迎有个人经验!如果你打算发布资源链接,请避免使用维基百科。我已经知道去哪找了。

与此相关,我想知道云服务数据库(如BigTable和SimpleDB)使用的非规范化方法。看这个问题。


连接表的顺序非常重要。如果您有两组数据,请尝试以一种方式构建查询,因此将首先使用最小的数据,以减少查询必须处理的数据量。

对于一些数据库来说,这并不重要,例如MS SQL在大多数情况下知道正确的连接顺序。 对于某些(如IBM Informix)来说,顺序决定了一切。


详述别人说过的话,

Joins are just cartesian products with some lipgloss. {1,2,3,4}X{1,2,3} would give us 12 combinations (nXn=n^2). This computed set acts as a reference on which conditions are applied. The DBMS applies the conditions (like where both left and right are 2 or 3) to give us the matching condition(s). Actually it is more optimised but the problem is the same. The changes to size of the sets would increase the result size exponentially. The amount of memory and cpu cycles consumed all are effected in exponential terms.

当我们非规格化时,我们完全避免了这种计算,就像在你的书的每一页上贴上一个彩色的粘性纸。你可以不使用参考而推断出信息。我们付出的代价是,我们损害了DBMS的本质(数据的最佳组织)。


去规范化以提高业绩?这听起来很有说服力,但根本站不住脚。

Chris Date和Ted Codd博士一起是关系数据模型的最初支持者,他对反对标准化的错误观点失去了耐心,并使用科学方法系统地推翻了这些观点:他获得了大型数据库并对这些断言进行了测试。

我想他是在《关系数据库著述1988-1991》中写的,但这本书后来被卷进了《数据库系统导论》的第6版,在我写这本书的时候,它已经是数据库理论和设计的权威文本了,在它的第8版中,而且很可能会在未来几十年里继续印刷。当我们大多数人还光着脚到处跑的时候,克里斯·戴特已经是这个领域的专家了。

他发现:

其中一些是针对特殊情况的 所有这些都不能用于一般用途 在其他特殊情况下,所有这些都要严重得多

这一切都归结于减小工作集的大小。涉及正确选择的键和正确设置的索引的连接是廉价的,而不是昂贵的,因为它们允许在行被物化之前对结果进行重要的修剪。

实现该结果需要大量磁盘读取,这是该操作中成本最高的一个数量级。相比之下,执行连接在逻辑上只需要检索键。在实践中,甚至不获取键值:键哈希值用于连接比较,减少了多列连接的成本,并从根本上降低了涉及字符串比较的连接的成本。不仅缓存容量大大增加,而且读取磁盘的工作量也大大减少。

此外,一个好的优化器会选择最严格的条件,并在执行连接之前应用它,非常有效地利用连接对高基数索引的高选择性。

诚然,这种类型的优化也可以应用于非规范化的数据库,但是那些想要非规范化模式的人在设置索引时通常不会考虑基数。

了解表扫描(在产生连接的过程中检查表中的每一行)在实践中很少发生是很重要的。查询优化器只在下列一个或多个条件成立时才会选择表扫描。

关联中的行数少于200行(在这种情况下,扫描会更便宜) 联接列上没有合适的索引(如果在这些列上联接是有意义的,那么为什么不为它们建立索引?修复它) 在比较列之前需要进行类型强制转换(WTF?!)修理它或者回家)参见结尾注释。网络问题 比较的参数之一是一个表达式(没有索引)

Performing an operation is more expensive than not performing it. However, performing the wrong operation, being forced into pointless disk I/O and then discarding the dross prior to performing the join you really need, is much more expensive. Even when the "wrong" operation is precomputed and indexes have been sensibly applied, there remains significant penalty. Denormalising to precompute a join - notwithstanding the update anomalies entailed - is a commitment to a particular join. If you need a different join, that commitment is going to cost you big.

如果有人想提醒我,这是一个不断变化的世界,我想你会发现,在更笨重的硬件上,更大的数据集只会夸大Date的发现的传播。

对于所有在计费系统或垃圾邮件生成器工作的人(你们真可耻),并愤怒地用手按键盘告诉我,你知道一个事实,非规范化更快,对不起,但你生活在一个特殊的情况下——具体地说,在这种情况下,你处理所有的数据,按顺序。这不是一般情况,你的策略是合理的。

你错误地概括它是没有道理的。有关在数据仓库场景中适当使用非规范化的更多信息,请参阅注释部分的末尾。

我还想回应

连接只是一些唇彩的笛卡尔积

真是一派胡言。限制会尽早实施,最严格的限制先实施。你读过这个理论,但你还没有理解它。连接仅被查询优化器视为“适用谓词的笛卡尔积”。这是一种符号表示(实际上是一种规范化),以促进符号分解,这样优化器就可以生成所有等效的转换,并根据成本和选择性对它们进行排序,以便选择最佳的查询计划。

让优化器产生笛卡尔积的唯一方法是不能提供谓词:SELECT * FROM a,B


笔记


David Aldridge提供了一些重要的补充信息。

除了索引和表扫描之外,确实还有很多其他的策略,而现代的优化器在产生执行计划之前会消耗掉这些策略。

一个实用的建议:如果它可以用作外键,那么就索引它,这样优化器就可以使用索引策略。

我曾经比MSSQL优化器更聪明。这在两个版本之前发生了改变。现在它教会了我。从一个非常真实的意义上说,它是一个专家系统,将许多非常聪明的人在一个非常封闭的领域中的所有智慧编成法典,以规则为基础的系统是有效的。


“胡扯”可能不太得体。他们要求我不要那么傲慢,并提醒我数学不会说谎。这是事实,但并不是所有数学模型的含义都必须从字面上理解。负数的平方根是非常方便的,如果你仔细避免检查它们的荒谬(双关语),并在你试图解释你的方程之前确保你把它们都消掉了。

我反应如此激烈的原因是声明的措辞是这样说的

连接是笛卡尔积…

这可能不是你想表达的意思,但这确实是写下来的,而且绝对不是真的。笛卡尔积是一种关系。连接是一个函数。更具体地说,连接是一个关系值函数。对于空谓词,它将产生笛卡尔积,检查它这样做是数据库查询引擎的正确性检查,但没有人在实践中编写无约束连接,因为它们在课堂之外没有实际价值。

我把它提出来是因为我不想让读者陷入把模型和被建模的东西混淆的古老陷阱。模型是一种近似值,为了方便操作而刻意简化。


选择表扫描连接策略的截止日期可能因数据库引擎而异。它受到许多实现决策的影响,如树节点填充因子、键值大小和算法的微妙之处,但广义上讲,高性能索引的执行时间为k log n + c。c项主要是由设置时间构成的固定开销,曲线的形状意味着(与线性搜索相比)直到n达到数百才会获得收益。


有时候,去规范化是个好主意

非规范化是对特定连接策略的承诺。如前所述,这会干扰其他连接策略。但是,如果您有大量的磁盘空间、可预测的访问模式以及处理大部分或全部数据的趋势,那么对连接进行预计算是非常值得的。

您还可以确定操作通常使用的访问路径,并预先计算这些访问路径的所有连接。这是数据仓库背后的前提,或者至少当它们由知道为什么要做他们正在做的事情的人构建时是这样,而不仅仅是为了遵从流行词汇。

通过规范化事务处理系统的批量转换,可以定期生成设计合理的数据仓库。这种操作和报告数据库的分离具有消除OLTP和OLAP(在线事务处理即数据输入和在线分析处理即报告)之间冲突的非常理想的效果。

这里重要的一点是,除了定期更新之外,数据仓库是只读的。这使得更新异常的问题变得毫无意义。

不要犯使OLTP数据库(数据输入发生的数据库)非规范化的错误。它可能更快的计费运行,但如果你这样做,你会得到更新异常。有没有试过让《读者文摘》停止给你寄东西?

现在磁盘空间很便宜,所以请自便。但非规范化只是数据仓库故事的一部分。更大的性能收益来自预先计算的汇总值:每月总计,诸如此类。它总是关于减少工作集。


ADO。NET问题与类型不匹配

假设您有一个SQL Server表,其中包含一个类型为varchar的索引列,并且您使用AddWithValue传递一个参数来约束对该列的查询。c#字符串是Unicode,因此推断的参数类型将是NVARCHAR,这与VARCHAR不匹配。

VARCHAR到NVARCHAR是一种扩展转换,所以它是隐式发生的——但是跟索引说再见吧,希望你能找到原因。


“计算磁盘的点击率”(里克·詹姆斯)

如果所有东西都缓存在RAM中,join的成本相当低。也就是说,规范化不会有太多的性能损失。

如果“规范化”模式导致join频繁地访问磁盘,但等效的“非规范化”模式不必访问磁盘,那么非规范化就会在性能竞争中胜出。

来自原作者的评论:现代数据库引擎非常擅长组织访问排序,以最大限度地减少连接操作期间的缓存丢失。上面所说的虽然是正确的,但可能会被误解为暗示连接在大数据上的代价必然是有问题的。这将导致缺乏经验的开发人员做出糟糕的决策。


大多数评论者没有注意到的是,在复杂的RDBMS中可以使用广泛的连接方法,而非规范化者总是掩盖维护非规范化数据的更高成本。并不是每个连接都基于索引,数据库有许多用于连接的优化算法和方法,旨在降低连接成本。

在任何情况下,连接的成本取决于它的类型和其他一些因素。它一点也不贵——举个例子。

A hash join, in which bulk data is equijoined, is very cheap indeed, and the cost only become significant if the hash table cannot be cached in memory. No index required. Equi-partitioning between the joined data sets can be a great help. The cost of a sort-merge join is driven by the cost of the sort rather than the merge -- an index-based access method can virtually eliminate the cost of the sort. The cost of a nested loop join on an index is driven by the height of the b-tree index and the access of the table block itself. It's fast, but not suitable for bulk joins. A nested loop join based on a cluster is much cheaper, with fewer logicAL IO'S required per join row -- if the joined tables are both in the same cluster then the join becomes very cheap through the colocation of joined rows.

数据库是为连接而设计的,它们在如何进行连接方面非常灵活,通常性能非常好,除非连接机制出错。


I think the whole question is based on a false premise. Joins on large tables are not necessarily expensive. In fact, doing joins efficiently is one of the main reasons relational databases exist at all. Joins on large sets often are expensive, but very rarely do you want to join the entire contents of large table A with the entire contents of large table B. Instead, you write the query such that only the important rows of each table are used and the actual set kept by the join remains smaller.

此外,您还具有Peter Wone提到的效率,这样在最终结果集物化之前,每条记录的重要部分只需要存储在内存中。此外,在具有许多连接的大型查询中,您通常希望从较小的表集开始,然后逐步到较大的表集,以便内存中保留的表集尽可能长时间地保持较小。

如果操作得当,联接通常是比较、组合或筛选大量数据的最佳方式。


瓶颈几乎总是磁盘I/O,甚至更具体地说——随机磁盘I/O(相比之下,顺序读取相当快,可以使用预读策略进行缓存)。

连接可以增加随机搜索——如果你在一个大表中跳跃读取一小部分。但是,如果查询优化器认为这样更好的话,它会将其转换为顺序表扫描(丢弃不需要的行)。

A single denormalized table has a similar problem - the rows are large, and so less fit on a single data page. If you need rows that are located far from another (and the large row size makes them further apart) then you'll have more random I/O. Again, a table scan may be forced to avoid this. But, this time, your table scan has to read more data because of the large row size. Add to that the fact that you're copying data from a single location to multiple locations, and the RDBMS has that much more to read (and cache).

对于2个表,您还可以获得2个聚集索引—并且通常可以索引更多(因为插入/更新开销更少),这可以极大地提高性能(主要是因为索引(相对)较小,可以快速从磁盘读取(或缓存成本低),并减少需要从磁盘读取的表行数量)。

联接的唯一开销来自于计算匹配的行。Sql Server使用3种不同类型的连接(主要基于数据集大小)来查找匹配的行。如果优化器选择了错误的连接类型(由于统计数据不准确、索引不足,或者只是优化器错误或边缘情况),就会极大地影响查询时间。

A loop join is farily cheap for (at least 1) small dataset. A merge join requires a sort of both datasets first. If you join on an indexed column, though, then the index is already sorted and no further work needs to be done. Otherwise, there is some CPU and memory overhead in sorting. The hash join requires both memory (to store the hashtable) and CPU (to build the hash). Again, this is fairly quick in relation to the disk I/O. However, if there's not enough RAM to store the hashtable, Sql Server will use tempdb to store parts of the hashtable and the found rows, and then process only parts of the hashtable at a time. As with all things disk, this is fairly slow.

在最佳情况下,这些操作不会导致磁盘I/O,因此从性能角度看可以忽略不计。

总之,在最坏的情况下——从x个连接表中读取相同数量的逻辑数据实际上应该比从单个非规格化表中读取更快,因为磁盘读取更小。要读取相同数量的物理数据,可能会有一些轻微的开销。

由于查询时间通常由I/O成本决定,并且数据的大小不会因非规范化而改变(减去一些非常微小的行开销),因此仅仅将表合并在一起并没有巨大的好处。倾向于提高性能的非规范化类型IME是缓存计算值,而不是读取计算这些值所需的10,000行。


当您考虑连接的复杂性类时,决定是去规范化还是规范化是相当简单的过程。例如,当查询为O(k log n)时,我倾向于用规范化设计数据库,其中k相对于所需的输出大小。

反规格化和优化性能的一个简单方法是考虑对规范化结构的更改如何影响反规格化结构。然而,这可能会有问题,因为它可能需要事务逻辑才能在非规范化的结构化上工作。

关于正常化和非正常化的争论不会结束,因为问题是巨大的。许多问题的自然解决方案需要两种方法。

作为一般规则,我总是存储可以重构的规范化结构和非规范化缓存。最终,这些缓存帮我解决了未来的规范化问题。