MySQL 的 Join 查询及 Hash Join 优化 | StoneDB 技术分享会 #3

news2024/10/6 8:25:30

 StoneDB开源地址

https://github.com/stoneatom/stonedb

图片

设计:小艾

审核:丁奇、宇亭

编辑:宇亭

作者一:徐鑫强(花名:无花果)
电子科技大学-计算机技术-在读硕士、StoneDB 内核研发实习生

作者二:柳湛宇(花名:乌淄)
浙江大学-软件工程-在读硕士、StoneDB 内核研发实习生

一、MySQL 连接方式简介

MySQL 支持自然连接、等值连接(内连接)、左连接、右连接、交叉连接五种连接方式,不支持全外连接,全外连接可以通过 Union 并集操作实现。连接算法:简单嵌套循环、索引嵌套循环、块嵌套循环以及哈希连接。

简单嵌套循环(Simple Nested-Loop Join,SNLJ)

驱动表中的每一条记录与被驱动表中的所有记录依次比较判断,驱动表遍历一次,被驱动表遍历多次。此算法开销非常大,假设驱动表的行数为 M,被驱动表的行数为 N,则算法时间复杂度为 O(M*N)。实际上,MySQL 并不会使用此算法。

图片

基于索引的嵌套循环(Indexed Nested-Loop Join,INLJ)

通过在被驱动表建立索引,减少被驱动表的扫描次数。一般 B+树的高度为 3~4 层,也就是说匹配一次的 I/O 消耗也就 3~4 次,因此索引查询的成本是比较固定的,故优化器都倾向于使用记录数少的表作为驱动表。在有索引的情况下,MySQL 会尝试使用此算法。整个过程如下图所示:

图片

基于块的嵌套循环(Block Nested-Loop Join,BNLJ)

扫描一个表的过程其实是先把这个表从磁盘上加载到内存中,然后在内存中比较匹配条件是否满足。但内存里可能并不能完全存放的下表中所有的记录。为了减少访问被驱动表的次数,我们可以首先将驱动表的数据批量加载到 Join Buffer(连接缓冲),然后当加载被驱动表的记录到内存时,就可以一次性和多条驱动表中的记录做匹配,这样可大大减少被驱动表的扫描次数,这就是 BNL 算法的思想。当被驱动表上没有建立索引时,MySQL 会尝试使用此算法。整体效率比较:INLJ > BNLJ > SNLJ。整个过程如下图所示:

图片

哈希连接(Hash Join)

嵌套循环连接对于被连接的数据集较小的情况是较好的选择,而对于大数据集 Hash Join 是更好的方式。优化器使用两个表中的较小表在内存中依据 Join Key 建立哈希表,然后依次扫描较大表并探测哈希表,找出能与哈希表匹配的行。Hash Join 会破坏表数据的有序性和局部性,因此它只能应用于等值连接。

二、哈希连接的优化方向简述

随着 MySQL 8.0 对 Hash Join 支持,在内外表均无索引或大表驱动小表的情况下,Hash Join 显然是比 BNLJ 更好的选择,而在 AP 场景下,大量数据多表 Join 的刚需也使得 Hash Join 有了多种方向的优化路径。本章简要介绍一部分对 Hash Join 优化的方向和思路。

一个关键的前提是,前述介绍提到了 Hash Join 不适用于非等值连接,因此当表连接语句语句中未使用ON <attr1>=<attr2>样式的等值连接方法或默认的自然等值连接时,MySQL 不会使用 Hash Join。

首先应当明确 Hash Join 的工作流程,以 MySQL 为例 [1] ,MySQL 就使用了经典的单线程 Hash Join 实现,它有建表(build)和探测(probe)生成结果两个步骤。

  1. 「建表」:如下图所示,建表就是遍历 OuterTable,将其等值连接键计算哈希值并根据结构构建为一个哈希表:

图片

  1. 「探测」:如下图所示,探测步骤就是遍历 InnerTable,计算每个 tuple 的值连接键的哈希值并从哈希表中找到对应的桶(bucket),并通过对比决定是否能进行哈希连接,进而完成整个 Hash Join 过程 [2] 。

图片

2.1 哈希算法的选取

哈希算法是 Hash Join 的基础,好的哈希算法可以极大提升依赖哈希操作的程序的效率。优秀的哈希函数会在随机性(体现发生哈希冲突的概率)和计算效率(单词计算哈希的速度)之间做 trade-off,因此不同侧重方向的数据库也应当选择合适的哈希函数,如 Apache Doris[3]选择了 CRC32 这一计算速度很快且可以通过 SIMD 加速的哈希函数,而 DuckDB 选择了随机性更高的 MurmurHash[4]。

但时至今日,随着 XXhash[5]的问世,哈希函数的选取似乎真正拥有了银弹。对于绝大多数的 HashTable 这种哈希长度相对较小的场景,XXHash 的不同长度变种似乎都是最佳选择:

图片

2.2 哈希表的基础结构设计

哈希表基础结构设计的一个基础点就是其处理冲突的方法,经典的处理方法有开放寻址法(即在哈希冲突时使用线性探测、平方探测、重哈希等方法继续在哈希表中搜索)和拉链法(在 bucket 中存储链表或平衡二叉树代表的冲突子项)。拉链法是更直观和更低哈希冲突率的做法,因此其多见于 Java/Go 等编程语言的哈希表实现。然而对于数据库内核中的哈希表,应用大数据量、控制内存使用量和 Cache Miss 率等都是优先级更高的需求,因此线性探测等开放寻址的冲突处理方法是构架哈希表的更优解。

另一个关键的基础结构设计是哈希表扩容的机制,大部分哈希表的扩容机制是在当已有元素站总容量比例超过一定阈值(对于 DuckDB,这个阈值是默认是 50%,对于 Java 的 HashMap,这个阈值默认是 75%)后进行扩容。但是显然对于线性探测的冲突处理方法,哈希冲突的概率由于哈希聚集(hash clustering)的原因会随占用率提高而迅速增加,因此一般线性探测的冲突处理方法设定的阈值较低,这也导致了内存的浪费。因此有一系列的工作[6] 通过 rehash 或维护细粒度数据结构等方法改善这一情况。

对于数据库内核这一领域,内核的优化器可以为哈希表提供可用的结构优化和先验知识。如对于列式存储数据库,可以通过 build 过程下推,直接以内核中读取的压缩后的键进行哈希表的构建合并,减少序列化和反序列化开销并减小 hash key 长度。此外,可以通过优化器的统计数据,将需要建表的数据剪去前缀,这也能进一步地减少 hash key 长度,进而加速哈希函数计算速度。

2.3 对探测过程的优化

由上节可以看出,对于线性探测方法构建的哈希表,哈希冲突是探测操作是的性能瓶颈,因此可以引入一层过滤层,尽早过滤掉不在哈希表中的探测键值,从而减少探测的次数,这一过滤层最经典的实现就是如下图所示的布隆过滤器[7]。

图片

本文不再深入阐述布隆过滤器的算法原理和优化方法,读者只需要知道,布隆过滤器可以通过若干个哈希函数(实践上,它们由一个基础哈希函数和位移、累加操作得到)操作一个 bitmap,通过对 OuterTable 的等值键遍历进行构建并由 Inner Table 探测。其特点是存在假阳性(False Positive),但不存在假阴性(False Negative),即通过布隆过滤器的记录并不一定真的能够匹配(可能存在哈希冲突),但被过滤掉的记录一定不匹配。

在布隆过滤器的基础上,还有一系列的概率数据结构变种,如 Block Bloom Filter,Cuckoo Filter[8]等 。不过对于不需要删除,操作相对固定的 Hash Join 场景,实现简单,占用内存少的布隆过滤器是大部分情况下的最佳选择。

2.4 对 Cache Miss 的优化

哈希表访存的随机性不可避免地会提升 cache miss 率而不得不通过页表访问内存,这会使得 Hash Join 遭遇性能瓶颈。现代计算机处理器的最大缓存粒度是 LLC(Last Layer Cache,在广泛应用的 Intel/AMD 处理器上,它是 L3 缓存),因此以 LLC 大小为单元的内存区域操作是对 Cache Miss 率优化的出发点。一个简单的方法是条件化预读取(Conditional Pre-fetching):哈希表可以维护关于哈希冲突数量、占有率以及哈希冲突热点区域的元数据,并根据这些元数据判断一次 probe 是否可能产生大量的哈希冲突,并在可能产生冲突时以 LLC 大小为单位预读取若干个 bucket 到内存,这样线性探测方法将可以减少 cache miss 数量。

更复杂的方法则是按如下图的方式构建分区哈希表(Partitioned Hash Table),即按照一定的标准(这个标准可以参考优化器提供的统计数据)将 Outer Table 在建表时放入不同的子哈希表(称为 Partition),而遍历 Inner Table 时,可以使用同样的标准将比较的 key 路由到对应的 Partition 中进行哈希查找和比对。这样做意义有三点

  1. 首先就是通过让每个 Partition 能够被装入 LLC,使得处理一个 Partition 的构建和探测任务时大大降低 Cache Miss 率;

  2. 可以更细粒度地管理哈希表的内存使用,哈希表可以不以 2 的幂的形式分配内存(以 Partition 为基本分配单位),同时在极限情况下也可以释放部分空的 Partition 以移作他用;

  3. 使得并行构建哈希表成为可能,这部分由 2.5 节阐述。

图片

接下来的问题是,如果快速且有效地将整个哈希表且分为若干分区表,为了保证这一过程的效率,Radix Hash Join[9]的流程被提出。

2.5 多线程哈希连接的优化

MySQL 的 Hash Join 是单线程执行的。但通过例如上述构建布隆过滤器和分区哈希表的方法,我们可以实现多线程执行。

布隆过滤器本身的构建和探测类似于哈希表的构建和探测,因此二者可以类比分析。布隆过滤器的变种 Blocked Bloom Filter 通过数学证明可以与布隆过滤器有类似的效能,但可以通过开多线程并行构建每个 Block,并空值 Block 大小适配 CPU 核的缓存,并通过 SIMD 加速探测操作。Hash Join 的探测操作类似,将 Inner Table 的记录切为若干个线程并发处理的任务段后,并行地对哈希表进行探测,并在需要时将最后的结果进行 Merge 操作以保证数据有序性,这一点类似于排序-归并连接的算法。

而构建操作则先得到更加复杂,因为它存在写操作写操作,即使是对 Partitioned Hash Table,仍然要进行 Partition-wise 或 Bucket-wise 的加锁-解锁操作才能并行执行。因此需要同步措施来保证线程之间数据的一致性和正确性,在目前的工业实践上,通过被大部分主流处理器支持的cmpxchg指令实现的 CAS(Compare And Swap)操作是 CPU 密集操作的最佳实践:

图片

本图引用自互联网,侵权联系删除

参考文献

  1. https://dev.mysql.com/blog-archive/hash-join-in-mysql-8/

  2. https://zhuanlan.zhihu.com/p/589601705

  3. github.com/apache/doris

  4. https://tanjent.livejournal.com/756623.html

  5. https://github.com/Cyan4973/xxHash

  6. Ronald Barber, Guy M. Lohman, Ippokratis Pandis, etc. Memory-Efficient Hash Joins. PVLDB, 8(4):353–364, 2014.

  7. https://en.wikipedia.org/wiki/Bloom_filter

  8. Bin Fan, David G. Andersen, Michael Kaminsky, & Michael Mitzenmacher (2014). Cuckoo Filter: Practically Better Than Bloom. In Proceedings of the 10th ACM International on Conference on emerging Networking Experiments and Technologies, CoNEXT 2014, Sydney, Australia, December 2-5, 2014 (pp. 75–88). ACM.

  9. https://github.com/giorgospan/Radix-Hash-Join

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

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

相关文章

BES 平台 SDK之代码架构讲解二

本文章是基于BES2700 芯片&#xff0c;其他BESxxx 芯片可做参考&#xff0c;如有不当之处&#xff0c;欢迎评论区留言指出。 BES 平台 SDK之代码架构讲解一_谢文浩的博客-CSDN博客 上篇文章粗略的对整个SDK 目录下的文件进行了说明&#xff0c;接下来会对SDK 比较详细的介绍。…

C语言实用调试详解

目录 什么是bug? 调试是什么?有多重要? 调试是什么? 调试的基本步骤 Debug和Release的介绍 Windows环境调试介绍 调试环境的准备 学会快捷键 调试的时候查看程序当前信息 查看临时变量的值 查看内存信息 查看调用堆栈 查看汇编信息 查看寄存器信息 一些调试…

HCIP 重发布+路由策略总结

重发布 在同一个网络拓结构中&#xff0c;如果存在多种不同的路由协议&#xff0c;由于不同路由协议的机制各有不同&#xff0c;对路由的处理也不相同&#xff0c;这就在网络中造成了路由信息的隔离&#xff0c;在路由器的边界路由器上&#xff0c;将某种路由协议的路由信息引…

[网络工程师]-网络规划与设计-网络故障分析与处理

网络环境越复杂,发生故障的可能性越大,引发故障的原因也就越难确定。网络故障往往具有特定的故障现象。这些现象可能比较笼统,也可能比较特殊。利用特定的故障排查工具及技巧,在具体的网络环境下观察故障现象,细致分析,最终必然可以查出一个或多个引发故障的原因。一旦能…

gitlab上传代码

输入 git clone https地址&#xff0c;此地址可以在&#xff0c;gitlab项目上拷贝到本地&#xff0c;看本地电脑会出现在gitlab上新建的项目&#xff0c;并进入该目录下 将要上传的代码拷贝到该目录 依次输入一下代码 git init &#xff08;用于在目录中创建新的 Git 仓库。…

打造独一无二的花店小程序,轻松搭建步骤详解

随着移动互联网的快速发展&#xff0c;花店也开始意识到拥有一个专属的小程序能够提升用户体验、增加销售额。那么&#xff0c;如何快速搭建一个漂亮、实用的花店小程序呢&#xff1f;下面就为大家介绍一下具体的步骤。 第一步&#xff0c;使用第三方制作平台。如乔拓云网是一个…

setEagerlyType字段理解

官方文档介绍&#xff1a;V5.0.4版本开始一对一关联预载入支持两种方式&#xff1a;JOIN方式&#xff08;一次查询&#xff09;和IN方式&#xff08;两次查询&#xff09;&#xff0c;如果要使用IN方式关联预载入&#xff0c;在关联定义方法中添加。 这句话的意思是jion方式关联…

阿里云服务器免费试用及搭建WordPress网站

文章目录 前言一、免费试用1、选择使用产品2、进行产品配置3、远程连接阿里云服务器①、重置实例密码②、SecureCRT 远程链接③、Workbench 远程链接二、搭建 WordPress 网站1、开放搭建 WordPress 需要的端口2、搭建 LAMP 环境①、Linux 系统升级和更新源②、安装 Apache2③、…

【Excel】记录Match和Index函数的用法

最近一直用到的两个处理EXCEL表格数据的函数向大家介绍一下&#xff0c;写这篇博文的目的也是为了记录免得自己忘记了&#xff0c;嘻嘻。 先上百度的链接 Match函数的用法介绍&#xff1a;https://jingyan.baidu.com/article/2fb0ba40b4933941f3ec5f71.html 小结&#xff1a;…

Java从入门到精通(二)· 基本语法

Java从入门到精通&#xff08;二&#xff09; 基本语法 一 变量 1.字面量 计算机是用来处理数据的&#xff0c;字面量就是告诉程序员&#xff1a;数据在程序中的书写格式。 特殊的字符&#xff1a; \n 表示换行&#xff0c; \t 表示一个制表符&#xff0c;即一个tab 2.变量…

【JVM】什么是双亲委派机制

文章目录 1、类加载机制2、双亲委派模型2.1、介绍2.2、为什么需要双亲委派2.3、源码解析 3、破坏双亲委派3.1、介绍3.2、破坏实现3.3、破坏双亲委派的例子 4、线程上下文类加载器 1、类加载机制 类加载阶段分为加载、连接、初始化三个阶段&#xff0c;而加载阶段需要通过类的全…

【Ansible】Ansible自动化运维工具之playbook剧本搭建LNMP架构

LNMP 一、playbooks 分布式部署 LNMP1. 环境配置2. 安装 ansble3. 安装 nginx3.1 准备 nginx 相关文件3.2 编写 lnmp.yaml 的 nginx 部分3.3 测试 nginx4. 安装 mysql4.1 准备 mysql 相关文件4.2 编写 lnmp.yaml 的 mysql 部分4.3 测试 mysql5. 安装 php5.1 编写 lnmp.yaml 的 …

健身时戴什么耳机比较好、健身用的运动耳机推荐

运动健身已经成为一种潮流&#xff0c;有的人为了追求马甲线和八大块腹肌&#xff0c;还有的人为了缓解学习和工作的压力。但你在运动健身的时候难免会有烦躁、疲惫的时候&#xff0c;如果这时有音乐的加入那就完美了&#xff0c;因为美妙的歌声能冲淡这种疲惫感&#xff0c;让…

面向金融科技方向选手!一级学会背书,AI选股与可视分析大赛来啦

金融量化领域邂逅人工智能&#xff0c;将会迸发出怎样的火花&#xff1f; 在深度学习、强化学习和自然语言处理等技术取得不断突破和创新的今天&#xff0c;AI如何赋能量化投资领域&#xff0c;助力开发者打造表现优异&#xff0c;更加安全可靠的量化模型&#xff1f; 第四届CS…

回收站清空的文件怎么恢复?文件恢复,就靠这3个方法!

“不小心把回收站清空了怎么办&#xff1f;之前在回收站里的文件还能恢复吗&#xff1f;诚心发问&#xff0c;希望大家给我出出主意。” 文件被删除之后通常在电脑的回收站中还可以还原。但实际使用电脑时&#xff0c;很多朋友为了释放电脑内存&#xff0c;都会有定期清空回收站…

【Redis】内存数据库 Redis 进阶

目录 分布式缓存 RedisRedis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 AOF (Appen…

租赁固定资产管理

智能租赁资产管理系统可以为企业单位提供RFID资产管理系统。移动APP资产管理&#xff0c;准确总结易损耗品和固定资金&#xff0c;从入库到仓库库存实时跟踪&#xff0c;控制出库和入库的全过程。同时&#xff0c;备件和耗材与所属资产设备有关&#xff0c;便于备件的申请和管理…

指针进阶详解续---C语言

❤博主CSDN:啊苏要学习 ▶专栏分类&#xff1a;C语言◀ C语言的学习&#xff0c;是为我们今后学习其它语言打好基础&#xff0c;C生万物&#xff01; 开始我们的C语言之旅吧&#xff01;✈ 目录 前言&#xff1a; 一.函数指针数组 二.指向函数指针数组的指针 三.回调函数 …

3D WEB轻量化渲染引擎Communicator发布冲突检测库!增加客户端和服务器端冲突检测功能

​HOOPS Communicator是Tech Soft 3D旗下的主流产品之一&#xff0c;具有强大的、专用的高性能图形内核&#xff0c;专注于基于Web的高级3D工程应用程序。其由HOOPS Server和HOOPS Web Viewer两大部分组成&#xff0c;提供了HOOPS Convertrer、Data Authoring的模型转换和编辑工…

【Java练习题汇总】《第一行代码JAVA》综合测试二,汇总Java练习题

Java练习题 综合测试二 1️⃣ 综合测试二 1️⃣ 综合测试二 下面( )不属于面向对象的特点。 A. 封装 B. 转型 C. 继承 D. 多态 下面关于类与对象的描述正确的是( )。 A. 任何情况下必须先有类再有对象&#xff0c;对象只能够调用类中定义的方法&#xff0c;不能够调用属性 B.…