Paul White Asked: 2019-07-18 12:47:55 +0800 CST2019-07-18 12:47:55 +0800 CST 2019-07-18 12:47:55 +0800 CST 哈希聚合救助 772 聊天讨论中出现的一个问题: 我知道哈希连接救助会在内部切换到一种嵌套循环的东西。 SQL Server 对散列聚合救助做了什么(如果它可能发生的话)? sql-server execution-plan 1 个回答 Voted Best Answer Paul White 2019-07-18T12:47:55+08:002019-07-18T12:47:55+08:00 散列连接和散列聚合都在内部使用相同的运算符代码,尽管散列聚合仅使用单个(构建)输入。Craig Freedman 描述了哈希聚合的基本操作: 与散列连接一样,散列聚合需要内存。在使用散列聚合执行查询之前,SQL Server 使用基数估计来估计我们需要多少内存来执行查询。使用散列连接,我们存储每个构建行,因此总内存需求与构建行的数量和大小成正比。连接的行数和连接的输出基数对连接的内存要求没有影响。使用散列聚合,我们为每个组存储一行,因此总内存需求实际上与输出组或行的数量和大小成正比。如果我们按列分组的唯一值和组更少,我们需要更少的内存。如果我们有更多的按列分组的唯一值和更多的组,我们需要更多的内存。 他继续谈论哈希递归: 那么,如果我们的内存不足会发生什么?同样,像哈希连接一样,如果内存不足,我们必须开始将行溢出到 tempdb。我们溢出一个或多个存储桶或分区,包括任何部分聚合的结果以及散列到溢出的存储桶或分区的任何其他新行。虽然我们不尝试聚合溢出的新行,但我们会对它们进行哈希处理并将它们分成几个桶或分区。一旦我们完成了所有输入组的处理,我们就会输出完成的内存组,并通过一次回读和聚合一个溢出的分区来重复算法。通过将溢出的行分成多个分区,我们减小了每个分区的大小,从而降低了算法需要重复多次的风险。 救助 Hash bailout的文档很少,但 Nacho Alonso Portillo 在What's the maximum level of recursion for the hash iterator before force bail-out 中提到过? 该值是产品中硬编码的常数,其值为五 (5)。这意味着,在散列扫描运算符对任何不适合工作区授予的内存的给定子分区使用基于排序的算法之前,之前必须发生五次将原始分区细分为较小分区的尝试。 提到的“哈希扫描运算符”引用CQScanHash了sqlmin.dll. 这个类负责实现我们在执行计划中看到的散列运算符(以所有形式,包括部分聚合和流不同)。 救助算法 这将我们带入您问题的核心 - 救助算法究竟是做什么的?它是“基于排序”还是基于“一种嵌套循环”? 可以说两者兼而有之,这取决于您的观点。当哈希递归达到第 5 级时,内存中的哈希分区从哈希表变为哈希值上最初为空的 b 树索引。在 b-tree 索引中查找来自单个先前溢出的散列分区的每一行,并酌情插入(新组)或更新(维护聚合)。 这一系列对 b 树的无序插入同样可以视为插入排序或索引嵌套循环查找。 在任何情况下,这个回退算法都保证最终完成而不分配更多内存。如果可用于 b-tree 的空间不足以容纳来自溢出分区的所有分组键和聚合,则可能需要多次传递。 一旦可用于保存 b-tree 索引的内存用完,任何进一步的行(来自当前溢出的分区)都将发送到单个新的tempdb分区(保证更小),并根据需要重复该过程。由于散列递归已经结束,溢出级别保持在 5。使用未记录的跟踪标志 7357 可以观察到一些处理细节。
散列连接和散列聚合都在内部使用相同的运算符代码,尽管散列聚合仅使用单个(构建)输入。Craig Freedman 描述了哈希聚合的基本操作:
他继续谈论哈希递归:
救助
Hash bailout的文档很少,但 Nacho Alonso Portillo 在What's the maximum level of recursion for the hash iterator before force bail-out 中提到过?
提到的“哈希扫描运算符”引用
CQScanHash
了sqlmin.dll
. 这个类负责实现我们在执行计划中看到的散列运算符(以所有形式,包括部分聚合和流不同)。救助算法
这将我们带入您问题的核心 - 救助算法究竟是做什么的?它是“基于排序”还是基于“一种嵌套循环”?
可以说两者兼而有之,这取决于您的观点。当哈希递归达到第 5 级时,内存中的哈希分区从哈希表变为哈希值上最初为空的 b 树索引。在 b-tree 索引中查找来自单个先前溢出的散列分区的每一行,并酌情插入(新组)或更新(维护聚合)。
这一系列对 b 树的无序插入同样可以视为插入排序或索引嵌套循环查找。
在任何情况下,这个回退算法都保证最终完成而不分配更多内存。如果可用于 b-tree 的空间不足以容纳来自溢出分区的所有分组键和聚合,则可能需要多次传递。
一旦可用于保存 b-tree 索引的内存用完,任何进一步的行(来自当前溢出的分区)都将发送到单个新的tempdb分区(保证更小),并根据需要重复该过程。由于散列递归已经结束,溢出级别保持在 5。使用未记录的跟踪标志 7357 可以观察到一些处理细节。