Hash Join(PostgreSQL 14 Internals翻译版)

news2025/1/12 22:55:09

一阶段哈希连接(One-Pass Hash Joins)

散列连接使用预构建的散列表搜索匹配的行。下面是一个使用这种连接的计划的例子:

在这里插入图片描述
第一阶段,哈希连接节点1调用哈希节点2,哈希节点2从其子节点提取整个内部行集,并将其放入哈希表中。

哈希表存储哈希键和值对,可以通过键快速访问值;搜索时间不依赖于哈希表的大小,因为哈希键或多或少均匀地分布在有限数量的桶之间。一个给定的键所在的桶是由该哈希键的哈希函数决定的;由于桶的数量始终是2的幂,因此取计算值的所需位数就足够了。

就像buffer cache一样,这个实现使用一个动态可扩展的哈希表,通过链接(chaining)来解决哈希冲突。

连接操作的第一阶段扫描内部集合,并为它的每一行计算散列函数。在连接条件(Hash Cond)中引用的列用作哈希键,而哈希表本身存储内部集合的所有查询字段。

如果整个哈希表可以容纳在内存中,那么哈希连接是最有效的,因为在这种情况下,执行器将管理一次处理数据。为此目的分配的内存块的大小受到work_mem × hash_mem_multiplier值的限制。

在这里插入图片描述
让我们运行EXPLAIN ANALYZE查看一下查询的内存使用统计数据:

在这里插入图片描述
嵌套循环连接对内部集和外部集的处理方式不同,而散列连接可以交换它们。较小的集合通常用作内部集合,因为它会产生较小的哈希表。

在本例中,整个表放入分配的缓存中:大约占用143MB (Memory Usage),包含4M =(2的22次方) 个内存桶。因此,连接在一次传递(batch)中执行。

但是如果查询只引用了一列,那么哈希表将填充111MB:

在这里插入图片描述
这是避免在查询中引用多余字段的另一个原因(举个例子,如果使用星号,可能会出现这种情况)。

所选择的桶数应该保证当哈希表完全填满数据时,每个桶平均只保存一行。更高的密度会增加哈希冲突率,使搜索效率降低,而不太紧凑的哈希表会占用太多内存。 桶的估计数量增加到最接近的2的幂。

如果估计的哈希表大小超过基于单行平均宽度的内存限制,则将应用两遍散列(two-pass hashing)。

在哈希表完全构建完成之前,哈希连接不能开始返回结果。

第二阶段 (此时已经构建了哈希表),hash Join节点调用其第二个子节点以获取外部行集。对于扫描的每一行,将在散列表中搜索匹配项。它需要计算连接条件中包含的外部集合的列的散列键。

在这里插入图片描述
找到的匹配项返回到父节点。

成本预估

我们已经讨论了基数估计;因为它不依赖于连接方法,所以我现在将重点放在成本估计上。

Hash节点的成本由其子节点的总成本表示。这是一个虚拟数字,只是填补了计划中的空缺。所有实际的估计都包含在Hash Join节点的成本中。

在这里插入图片描述
连接的启动成本主要反映了创建哈希表的成本,包括以下部分:

  • 获取构建哈希表所需的内部集合的总成本
  • 计算连接键中包含的所有列的哈希函数的成本,对于内部集合的每一行(估计为cpu_operator_cost 每个操作)
  • 将所有内部行插入哈希表的成本(估计为cpu_tuple_cost每插入一行)
  • 获取外部行集的启动成本,这是启动连接操作所必需的

总成本包括启动成本和连接本身的成本,即:

  • 对于外部集合的每一行,计算连接键中包含的所有列的哈希函数的成本(cpu_operator_cost)
  • 重新检查连接条件的成本,这是解决可能的哈希冲突所必需的(估计为每个检查的操作符的cpu_operator_cost)
  • 每个结果行的处理成本(cpu_tuple_cost)

所需复核的次数是最难估计的。它是通过将外部集合的行数乘以内部集合(存储在哈希表中)的某个分数来计算的。为了估计这个分数,计划者必须考虑到数据分布可能不均匀。

因此,我们的查询成本估计如下:

在这里插入图片描述
在这里插入图片描述
这是依赖关系图:
在这里插入图片描述

双阶段哈希连接(Two-Pass Hash Joins)

如果规划器的估计显示哈希表将超过分配的内存,则将内部的行集分成若干批,分别进行处理。批的数量(就像桶的数量)总是2的幂;要使用的批处理由哈希键的相应位数决定。

任意两个匹配的行属于同一个批处理:放置在不同批处理中的行不能具有相同的哈希码。

所有批都持有相同数量的哈希键。如果数据均匀分布,批大小也将大致相同。计划器可以通过选择适当数量的批来控制内存消耗。

第一阶段,执行程序扫描内部行集以构建散列表。如果扫描的行属于第一批,则将其添加到哈希表中并保存在内存中。否则,它将被写入临时文件(每个批处理都有一个单独的文件)

会话可以存储在磁盘上的临时文件的总量是由temp_file_limit参数限制的(临时表不包括在这个限制中)。一旦会话达到这个值,查询就会终止。

在这里插入图片描述
第二阶段,扫描外部集合。如果该行属于第一批,它将与包含内部集合的第一批行的哈希表进行匹配(无论如何,在其他批中不可能有匹配)。

如果该行属于不同的批处理,则将其存储在临时文件中,该文件将为每个批处理单独创建。因此,N批可以使用2(N−1)个文件(如果某些批为空,则可以使用更少)。

一旦第二阶段完成,为哈希表分配的内存将被释放。此时,我们已经有了其中一个批次的连接结果。

在这里插入图片描述
对于保存在磁盘上的每批数据,都要重复这两个阶段:内部数据集的行从临时文件转移到哈希表;然后从另一个临时文件中读取与同一批处理相关的外部集的行,并与此哈希表进行匹配。一旦处理,临时文件将被删除。

在这里插入图片描述
与One-pass连接的类似输出不同,two-pass连接的EXPLAIN命令的输出包含多个批处理。如果使用BUFFERS选项,该命令还显示磁盘访问的统计信息:

在这里插入图片描述
在这里插入图片描述
我已经用增加的work_mem设置展示了上面的查询。默认值4MB对于整个哈希表来说太小了,无法容纳内存;在这个例子中,数据被分成64个批次,哈希表使用64K = (2的16次方)个桶。在构建哈希表(Hash节点)时,数据被写入临时文件(temp written);在连接阶段(Hash Join节点),读取和写入临时文件(temp read,written)。

要收集更多关于临时文件的统计信息,可以将log_temp_files参数设置为零。然后,服务器日志将列出所有临时文件及其大小(在删除时显示的大小)。

动态调整

两个问题可能打乱计划的事件进程:不准确的统计和不均匀的数据分布。

如果连接键列中值的分布不均匀,则不同批次将具有不同的大小。

如果某个批处理(第一个批处理除外)太大,则必须将其所有行都写入磁盘,然后再从磁盘读取。最麻烦的是外部集合,因为它通常更大。因此,如果外部集的mcv上有常规的非多元统计信息(即,外部集由表表示,连接由单列执行),则具有与mcv对应的哈希码的行被认为是第一批的一部分。这种技术(称为倾斜优化)可以在一定程度上减少两次连接的I/O开销。

由于这两个因素,一些(或全部)批的大小可能超过估计。然后,相应的哈希表将不适合分配的内存块,并将超过定义的限制。

因此,如果构建的哈希表太大,批处理的数量就会增加(翻倍)。每个批处理实际上被分成两个新的批处理:大约一半的行(假设分布是均匀的)留在哈希表中,而另一半保存到一个新的临时文件中。

即使最初计划了一次连接,也可能发生这种分离。事实上,一次和两次连接使用由相同代码实现的相同算法;我在这里单独列出它们只是为了更流畅地叙述。

批次数量不能减少。如果计划器高估了数据大小,则不会将批合并在一起。

在不均匀分布的情况下,增加批次数量可能没有帮助。例如,如果键列在其所有行中包含一个相同的值,则它们将被放入同一个批处理中,因为散列函数将一次又一次地返回相同的值。不幸的是,在这种情况下,无论施加了什么限制,哈希表都将继续增长。

为了演示批数量的动态增长,我们首先必须执行一些操作:

在这里插入图片描述
在这里插入图片描述
结果,我们得到一个名为bookings_copy的新表。它是booking表的精确副本,但是计划器将其中的行数低估了10倍。如果为另一个连接操作生成的一组行生成散列表,则可能出现类似的情况,因此没有可靠的统计信息可用。

这个错误的计算使计划器认为8个桶足够了,但是当执行连接时,这个数字增长到32:

在这里插入图片描述

成本预估

我已经使用这个示例来演示单次连接的成本估计,但是现在我要将可用内存的大小减少到最小,因此计划器将不得不使用两个批处理。它增加了连接的成本:
在这里插入图片描述
第二次传递的代价是将行溢出到临时文件中并从这些文件中读取它们。

两遍连接的启动成本是基于单遍连接的启动成本,这是由于写入尽可能多的页面以存储内部集所有行的所有必要字段的估计成本而增加的。虽然在构建哈希表时没有将第一批数据写入磁盘,但估计没有考虑到这一点,因此不依赖于批的数量。

反过来,总成本包括一次连接的总成本和读取先前存储在磁盘上的内部集的行以及读取和写入外部集的行的估计成本。

由于假定I/O操作是顺序的,因此写入和读取都以seq_page_cost 每个页面来估计。

在这个特殊的情况下,内部集合所需的页面数估计为7,而外部集合的数据预计适合2309页。将这些估计值添加到上面计算的一次连接成本中,我们得到与查询计划中显示的相同的数字:

在这里插入图片描述
因此,如果没有足够的内存,连接将分两次执行,并且效率会降低。因此,重要的是要注意以下几点:

  • 查询必须以一种从哈希表中排除冗余字段的方式组成。
  • 在构建哈希表时,规划器必须选择两组行中较小的那一组。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1105127.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 面向对象初步

目录 1 面向对象和面向过程区别1.1 面向过程(Procedure Oriented)思维1.2 面向对象(Object Oriented)思维1.3 面向对象思考方式1.4 面向对象和面向过程的总结 2 对象的进化3 类的定义4 __init__构造方法和__new__方法5 实例属性和实例方法5.1 实例属性5.2 实例方法5.2.1 实例对…

【学术】知云文献及划词翻译软件(XTranslator)的安装及使用

文章目录 一、知云文献翻译1.1 知云文献翻译是什么1.2 知云文献翻译下载地址1.3 知云文献翻译安装1.4 知云文献翻译使用1.4.1 使用方法1.4.2 解除限制1.4.3 软件特点1.4.4 翻译PDF 1.5 Windows版使用文档1.6 解锁所有翻译引擎 二、知云划词翻译(Xtranslator)2.1 知云划词翻译(X…

C# Winform编程(5)菜单和菜单组件

菜单和菜单组件 添加菜单编辑菜单 添加菜单 将MenuStrip控件拖拽到Form窗体顶部添加菜单 编辑菜单 添加菜单项,编辑菜单属性等功能。 右键单击已添加的菜单项可以弹出右键菜单: 可以设置菜单图标,使能菜单,显示快捷键、转换菜…

提高三维模型数据的几何坐标精度需要采取方法浅析

提高三维模型数据的几何坐标精度需要采取方法浅析 要提高倾斜摄影三维模型数据的几何坐标精度,可以采取以下方法: 选择合适的倾斜角度:倾斜角度对于几何坐标精度具有重要影响。过小的倾斜角度可能导致图像中特征点不足以提供准确的位置信息&…

10数据库-基础

四、数据库 15、MySQL 数据库优化 SQL优化 mysql优化 一、避免不走索引的场景尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。尽量避免使用not in,会导致引擎走全表扫描。尽量避免使用 or,会导致数据库引擎放弃索引进行…

[opencv]图像和特征点旋转

本来说这是很简单的一个内容,图像旋转只需要使用opencv中自带的旋转函数即可完成,但是最近在做特征点旋转的时候发现使用内置rotate函数给图像旋转90度,再用getRotationMatrix2D得出的旋转矩阵对特征点旋转,画出来的特征点位置全部…

零基础学习HTML5

1. 使用软件 vscode 谷歌浏览器 vscode下载地址:https://code.visualstudio.com/ 谷歌可以使用360软件管家安装 2. 安装插件 在vscode中安装插件:open in browser,点击Extensions后搜索对应插件名然后点击安装Install 安装完成后可在htm…

【LeetCode】543. 二叉树的直径

543. 二叉树的直径(简单) 思路 对于任一结点,以此结点为根的diameter就可以表示为左子树高度 右子树高度,而二叉树的diameter就是所有结点为根的diameter中最大的那个。因此,变量 maxLen 用来保存当前遍历过的节点的…

phpstudy_2016-2018_rce_backdoor 漏洞复现

phpstudy_2016-2018_rce_backdoor 漏洞复现 Remote Command Execute 打开 bp 打开代理浏览器 访问 php 页面 回到 bp 查看 http 历史,找到刚刚访问的 php 页面 发送到 Repeater 转到 Repeater php 页面请求内容加 Accept-Charset: 修改 Accept-Encodi…

Comsol电磁铁仿真

简介 Comsol是一款多物理场仿真软件,可以完成固体力学、流体力学、传热学和电磁学的仿真。本文将介绍使用Comsol完成电磁铁仿真的主要流程,计算铁芯的受力。 步骤 1.建立模型并设置材料 建立二维旋转对称模型,绿色为动铁,材料…

STM32F103外部晶振8MHZ改为16MHz的使用

STM32F103外部晶振8MHZ改为16MHz的使用 目录 STM32F103外部晶振8MHZ改为16MHz的使用前言一、修改标准函数库的方法1、stm32f10x.h修改HSE_VALUE2 、system stm32f10x.c的SetSysClockTo72()函数修改3、不同晶振的统一配置的方式4 、时间晶振修改5、修改Ta…

基于STM32F407的FreeRTOS学习笔记(1)——环境搭建

以前使用STM32单片机一直停留在逻辑开发以及前后台系统,而真正被广泛使用的则是RTOS。 前后台系统则是我们常用的,使用一个主循环许多的调用函数这些构成了后系统,利用中断进行异常处理则是前系统。 而RTOS则是将任务按照优先级排列&#xf…

怎么压缩图片?图片过大这样压缩变小

在日常生活中,我们常常会遇到需要上传或发送图片的情况,然而,很多时候图片的大小会成为问题,因为过大的图片可能会导致传输速度变慢,甚至无法上传。那么,如何将这些过大的图片压缩变小呢? 一、嗨…

【C++进阶之路】类型转换

文章目录 类型转换1.C语言的类型转换1.1整形提升1.2算术转换1.3强制类型转换 2.C类型转换2.1static_cast2.2reinterpret_cast2.3const_cast2.3dynamic_cast 总结 类型转换 1.C语言的类型转换 1.1整形提升 在写顺序表的插入函数时,我们的接口实现是这样的&#xf…

Confluence 自定义展示页面

1. 概述 Confluence 作为知识库可通过JS脚本方式&#xff0c;根据登录用户或用户组进行前端页面的自定义 2. 实现方式 Confluence →管理→自定义HTML 嵌入对应JS脚本&#xff0c;示例如下 <script type"text/javascript">jQuery(#footer).html(<div>…

UE5 Python脚本自动化Sequence Key帧

前言 码上1024了&#xff0c;给大家分享一个UE5的脚本小功能&#xff0c;UE5中Sequence动态Key功能&#xff0c;这样我们就可以根据我们的数据动态更新了&#xff0c;非常实用&#xff0c;适合刚入门或者小白&#xff0c;接下来我就把整个过程分享给大家。 过程 新建一个工程…

E049-论坛漏洞分析及利用-针对bwapp进行web渗透测试的探索

课程名称&#xff1a; E049-论坛漏洞分析及利用-针对bwapp进行web渗透测试的探索 课程分类&#xff1a; 论坛漏洞分析及利用 --------------------------------------------------------------------------------------------------------------------------------- 实验等…

用一段爬虫代码爬取高音质音频示例

以下是一个使用Reachability库和Objective-C编写的爬虫程序&#xff0c;用于爬取高音质的免费音频。通过https://www.duoip.cn/get_proxy的代码示例完美抓取数据。 #import <Foundation/Foundation.h> #import <Reachability/Reachability.h>interface AudioCrawle…

vue3学习(八)--- 组件相关

文章目录 全局组件批量注册全局组件 局部组件递归组件组件定义名称方式1.增加一个script 通过 export 添加name2.直接使用文件名当组件名3.使用插件 unplugin-vue-define-options 动态组件异步组件 一个 Vue 组件在使用前需要先被“注册”&#xff0c;这样 Vue 才能在渲染模板时…

从实时数据库转战时序数据库,他陪伴 TDengine 从 1.0 走到 3.0

关于采访嘉宾 在关胜亮的学生时代&#xff0c;“神童”这个称号如影随形&#xff0c;很多人初听时会觉得这个称谓略显夸张&#xff0c;有些人还会认为这是不是就是一种调侃&#xff0c;但是如果你听说过他的经历&#xff0c;就会理解这一称号的意义所在了。 受到教师母亲的影…