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

 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android 卡顿分析与布局优化

一、什么是卡顿&#xff1f;或者说我们怎么感知APP卡顿&#xff1f; 这里面涉及到android UI渲染机制&#xff0c;我们先了解一下android UI是怎么渲染的&#xff0c;android的View到底是如何一步一步显示到屏幕上的&#xff1f; android系统渲染页面流程&#xff1a; 1&…

重新审视MHA与Transformer

本文将基于PyTorch源码重新审视MultiheadAttention与Transformer。事实上&#xff0c;早在一年前博主就已经分别介绍了两者&#xff1a;各种注意力机制的PyTorch实现、从零开始手写一个Transformer&#xff0c;但当时的实现大部分是基于d2l教程的&#xff0c;这次将基于PyTorch…

使用javax.validation.constraints进行数据验证

使用javax.validation.constraints进行数据验证 在Java应用中&#xff0c;数据的验证是一个很重要的部分&#xff0c;特别是在接收用户输入或处理外部数据时。为了简化和标准化数据验证的过程&#xff0c;Java提供了javax.validation.constraints包&#xff0c;其中包含一系列注…

乳腺癌CT影像数据的深度学习:R语言与ANN神经网络构建高性能分类诊断模型

一、引言 乳腺癌是全球最常见的女性恶性肿瘤之一&#xff0c;也影响着男性的健康。据统计&#xff0c;每年有数百万人被诊断出患有乳腺癌[1]。乳腺癌的早期检测和准确诊断对于治疗和预后至关重要。然而&#xff0c;乳腺癌的早期诊断面临许多挑战&#xff0c;如图像解读的主观性…

uniapp 微信小程序:v-model双向绑定问题(自定义 props 名无效)

uniapp 微信小程序&#xff1a;v-model双向绑定问题&#xff08;自定义 props 名无效&#xff09; 前言问题双向绑定示例使用 v-model使用 v-bind v-on使用 sync 修饰符 参考资料 前言 VUE中父子组件传递数据的基本套路&#xff1a; 父传子 props子传父 this.$emit(事件名, …

Linux安装VScode

从本篇开始&#xff0c;打算有时间就写写在VScode中编写一些ros相关的案例程序用于学习记录。本篇是如何在Linux安装VScode的第一篇。 一、下载VScode 在Linux中打开浏览器输入&#xff1a;https://code.visualstudio.com/Download&#xff0c;选择与你电脑相匹配的版本下载&…

AssertionError: CUDA_HOME does not exist, unable to compile CUDA op(s)

安装deepspeed的时候出现如下错误&#xff1a; 检查是否有CUDA&#xff1a; 根据提示安装&#xff1a; 安装完之后检测&#xff0c;重新安装&#xff0c;成功安装。 参考资料 A100单机多卡大模型训练踩坑记录&#xff08;CUDA环境、多GPU卡住且显存100%&#xff09;

socket 基础

Socket是什么呢&#xff1f; ① Socket通常也称作“套接字”&#xff0c;用于描述IP地址和端口&#xff0c;是一个通信链的句柄。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。 ② Socket是连接运行在网络上的两个程序间的双向通信的端点。 ③ 网络通讯其实指…

STM32基础回顾

文章目录 单片机编程的原理GPIO中断EXTI外部中断定时器中断、串口中断 定时器定时器中断配置过程通用定时器输出比较功能&#xff1a;PWM波的生成定时器的输入捕获功能主从触发模式PWMI模式 定时器的编码器接口 DMA简介通信接口USART软件配置流程&#xff1a;1、仅发数据的配置…

校园跑腿小程序功能分享

提起校园跑腿小程序大家都不陌生&#xff0c;尤其是对上大学的伙伴们来说,更是熟悉得不能再熟悉了&#xff0c;和我们的生活息息相关&#xff0c;密不可分。 对于现在的年轻人来说&#xff0c;网购是非常简单和方便的一种购物方式&#xff0c;随之快递也会越来越多。在我们国家…

java版本spring cloud 企业工程系统管理 工程项目管理系统源码

&#xfeff; Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&…

self-attention笔记

self-attention 对于self-attention的理解 对于self-attention&#xff0c;我们直觉可能会觉得是从一个大的数据中&#xff0c;将我们的注意力集中在我们感兴趣的区域里&#xff0c; 但通过self-attention的原理可以发现&#xff0c;其原理更像是对于一个区域&#xff08;一个…

八大排序算法--希尔排序(动图理解)

目录 希尔排序 概念 算法思路 动画演示 代码如下 复杂度分析 时间复杂度测试 运行结果 完整代码 创作不易&#xff0c;如果本篇博客对您有一定的帮助&#xff0c;大家记得留言点赞哦。 希尔排序 概念 希尔排序是插入排序的一种&#xff0c;是对直接插入排序的优化。其…

uniapp小程序,根据小程序的环境版本,控制的显页面功能按钮的示隐藏

需求&#xff1a;根据小程序环境控制控制页面某个功能按钮的显示隐藏&#xff1b; 下面是官方文档和功能实现的相关代码&#xff1a; 实现上面需要&#xff0c;用到了uni.getAccountInfoSync()&#xff1a; uni.getAccountInfoSync() 是一个 Uniapp 提供的同步方法&#xff0c…

Acwing.875 快速幂

题目 给定n组ai , bi, pi&#xff0c;对于每组数据&#xff0c;求出akimod pi的值。 输入格式 第一行包含整数n。 接下来n行&#xff0c;每行包含三个整数ai , bi,pi。输出格式 对于每组数据&#xff0c;输出一个结果&#xff0c;表示aibimod pi的值。 每个结果占一行。 数…

Linux - 环境变量

1.基本概念 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪里&#xff0c;但 是照样可以链接成功&#xff0c;生…

「如何优雅有效利用周末和下班时间?」

文章目录 每日一句正能量前言下班的时间规划周末的时间规划提升周末体验感的好方法怎样才能获得充分的休息后记 每日一句正能量 眼望古城街尽&#xff0c;心谱落愁无序&#xff0c;旧时的誓言&#xff0c;曾而相似&#xff0c;河水在遵循河道的指引下&#xff0c;在曲折前进中放…

zookeeper学习(三)基础数据结构

数据模型 在 zookeeper 中&#xff0c;可以说 zookeeper 中的所有存储的数据是由 znode 组成的&#xff0c;节点也称为 znode&#xff0c;并以 key/value 形式存储数据。 整体结构类似于 linux 文件系统的模式以树形结构存储。其中根路径以 / 开头。 进入 zookeeper 安装的 …

心法利器[93] | 谈校招:技术面

心法利器 本栏目主要和大家一起讨论近期自己学习的心得和体会&#xff0c;与大家一起成长。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。 2022年新一版的文章合集已经发布&#xff0c;累计已经60w字了&#xff0c;获取方式看这里&…

大数据面试题:HBase的RegionServer宕机以后怎么恢复的?

面试题来源&#xff1a; 《大数据面试题 V4.0》 大数据面试题V3.0&#xff0c;523道题&#xff0c;679页&#xff0c;46w字 可回答&#xff1a;1&#xff09;HBase一个节点宕机了怎么办&#xff1b;2&#xff09;HBase故障恢复 参考答案&#xff1a; 1、HBase常见故障 导…