Answers:
哈希联接和哈希聚合在内部都使用相同的运算符代码,尽管哈希聚合仅使用单个(构建)输入。的基本操作散列集合体是由Craig弗里德曼描述:
与哈希联接一样,哈希聚合需要内存。在使用散列聚合执行查询之前,SQL Server使用基数估计来估计执行查询所需的内存量。通过散列连接,我们存储每个构建行,因此总内存需求与构建行的数量和大小成比例。联接的行数和联接的输出基数对联接的内存要求没有影响。对于散列聚合,我们为每个组存储一行,因此总内存需求实际上与输出组或行的数量和大小成正比。如果我们具有较少的按列分组的唯一值和较少的分组,则需要较少的内存。如果我们有更多的按列分组的唯一值和更多的分组,则需要更多的内存。
他继续谈论散列递归:
那么,如果我们的内存不足怎么办?再次,像哈希联接一样,如果我们内存不足,则必须开始将行溢出到tempdb。我们溢出一个或多个存储桶或分区,包括任何部分汇总的结果,以及散列到溢出存储桶或分区的任何其他新行。尽管我们不尝试汇总溢出的新行,但是我们对它们进行哈希处理并将其分成几个存储区或分区。处理完所有输入组后,我们将输出完整的内存中组,并通过一次回读并汇总一个溢出的分区来重复该算法。通过将溢出的行划分为多个分区,我们减小了每个分区的大小,从而降低了算法需要重复多次的风险。
哈希纾困的内容很少,但Nacho Alonso Portillo在强制纾困之前,哈希迭代器的最大递归级别是多少?
该值是一个常数,在产品中进行了硬编码,其值为五(5)。这意味着在哈希扫描运算符针对不适合工作空间授予的内存的任何给定子分区求助于基于排序的算法之前,必须先进行五次将原始分区细分为较小分区的尝试。
该“散列扫描运算符”中提到存在对内部类的引用CQScanHash
在sqlmin.dll
。此类负责执行计划中看到的哈希运算符(包括其所有形式,包括部分聚合和不同流程)的所有实现。
这使我们成为您提出问题的核心-纾困算法到底做了什么?是“基于排序”还是基于“某种嵌套循环”?
根据您的观点,这可以说是两者。当哈希递归达到级别5时,内存中的哈希分区将从哈希表更改为哈希值上最初为空的b树索引。在b树索引中查找单个先前散列的哈希分区中的每一行,并根据需要插入(新组)或更新(维护聚合)。
对b树的这一系列无序插入同样可以视为插入排序或索引嵌套循环查找。
无论如何,都可以保证该后备算法最终可以完成而无需分配更多的内存。如果可用于b树的空间不足以容纳来自溢出分区的所有分组键和聚集,则可能需要多次通过。
一旦可用于保存b树索引的内存耗尽,任何其他行(来自当前溢出的分区)将发送到单个新的tempdb分区(保证会更小),并根据需要重复该过程。由于哈希递归已结束,因此溢出级别保持在5 。使用未记录的跟踪标志7357可以观察到一些处理细节。