如何进行JOIN优化

如何进行JOIN优化

简单来说,JOIN是MySQL用来进行联表操作的,用来匹配两个表的数据,筛选并合并符合我们要求的结果集,但使用了Join我们一般会对它多一点关注,在java开发手册中,禁止三个表以上关联使用Join,而对两个Join一般也要进行一个严格的评估,再它的算法中,有简单的嵌套循环连接,索引循环连接,块嵌套循环连接,但本质都是对内层表,也就是被驱动表做一个次数的优化,再普通的双层for循环到内层采用索引再到内层使用缓存,所以就得出一下几点:

  • 1.永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量)

  • 2.为匹配的条件增加索引(减少内层表的循环匹配次数)

  • 3.增大join buffer size的大小(一次缓存的数据越多,那么内层包的扫表次数就越少)

  • 4.减少不必要的字段查询(字段越少,join buffer 所缓存的数据就越多)

而使用Join本身,再后续数据量的增长,Join的效率就低了,join查询会导致大量数据进入数据库的内存,还有热数据被淘汰的可能,导致内存命中率降低,也有逻辑稍微复杂,跨领域职责的情况,导致后续系统解耦,拆表、库、服务,都是一定影响

定义

JOIN是MySQL用来进行联表操作的,用来匹配两个表的数据,筛选并合并符合我们要求的结果集

常用的联接方式有:
  1. 左外连接 LEFT JOIN

  2. 右外连接 RIGHT JOIN

  3. 内连接 INNER JOIN

什么是驱动表?

  • 多表关联查询时,第一个被处理的表就是驱动表(外层),使用驱动表去关联其他表(内层)。

  • 驱动表的确定非常的关键,会直接影响多表关联的顺序,也决定后续关联查询的性能

驱动表的选择要遵循一个规则:

  • 在对最终的结果集没有影响的前提下,优先选择结果集最小的那张表作为驱动表

三种JOIN(执行)算法

1.Simple Nested-Loop Join(简单的嵌套循环连接)

简单来说,内外层遍历,两个for循环

  • 简单来说嵌套循环连接算法就是一个双层for 循环,通过循环外层表的行数据,逐个与内层表的所有行数据进行比较来获取结果

  • 这种算法是最简单的方案,性能也一般。对内循环没优化。

  • 例如有这样一条SQL:

 -- 连接用户表与订单表连接条件是 uid =ouser idselect * from user tl left join order t2 on tl.id = t2.user_id;-- user表为驱动表order表为被驱动表​
  • 转换成代码执行时的思路是这样的:

 for(user表行 uRow : user表){for(0rder表的行 oRow : order表){if(uRow.id = oRow.user_id){return uRow;}}}
  • 匹配过程如图

SNL的特点

  • 简单粗暴容易理解,就是通过双层循环比较数据来获得结果

  • 查询效率会非常慢假设A表有N行,B表有M行。SNL的开销如下:

    • A 表扫描 1 次

    • B 表扫描M次。

    • 一共有 N个内循环,每个内循环要M次,一共有内循环 N*M次

2.Index Nested-Loop Join ( 索引嵌套循环连接)

简单来说,外层遍历,内层索引,减少内层匹配数据次数

  • Index Nested-Loop Join 其优化的思路: 主要是为了减少内层表数据的匹配次数,最大的区别在于,用来进行join的字段已经在被驱动表中建立了索引。

  • 从原来的 匹配次数 = 外层表行数 x内层表行数,变成了匹配次数 外层表的行数x内层表索引的高度,极大的提升了join性能。

  • 当order表的user_id为索引时,执行过程会如下图:

3. Block Nested-Loop Join( 块嵌套循环连接)

简单来说,外层遍历,内层从缓存中找。

如果 join 的字段有索引,MySQL 会使用INL 算法。如果没有的话,MySQL 会如何处理? 因为不存在索引了,所以被驱动表需要进行扫描。这里 MySQL 并不会简单粗暴的应用SNL算法,而是加入了buffer 缓冲区,降低了内循环的个数,也就是被驱动表的扫描次数。

  • 在外层循环扫描 user表中的所有记录。扫描的时候,会把需要进行 join 用到的列都缓存到 buffer 中。buffel中的数据有一个特点,里面的记录不需要一条一条地取出来和 order 表进行比较,而是整个 buffer 和 order表进行批量比较。

  • 如果我们把 buffer 的空间开得很大,可以容纳下 user 表的所有记录,那么 order 表也只需要访问一次。

  • MySQL默认 buffer 大小256K,如果有n个join 操作,会生成n-1 个join buffer。

JOIN优化总结
  • 1.永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量)

  • 2.为匹配的条件增加索引(减少内层表的循环匹配次数)

  • 3.增大join buffer size的大小(一次缓存的数据越多,那么内层包的扫表次数就越少)

  • 4.减少不必要的字段查询(字段越少,join buffer 所缓存的数据就越多)

在这些优化方法中:

  1. BKA 优化是 MySQL 已经内置支持的,建议你默认使用;

  2. BNL 算法效率低,建议你都尽量转成 BKA 算法。优化的方向就是给被驱动表的关联字段加上索引;

  3. 基于临时表的改进方案,对于能够提前过滤出小数据的 join 语句来说,效果还是很好的;

  4. MySQL 目前的版本还不支持 hash join,但你可以配合应用端自己模拟出来,理论上效果要好于临时表的方案。

更详细:

一、简单的嵌套循环连接(Simple Nested Loop Join)

  1. 基本原理

这是最基础、最直观的连接算法,就如同两层嵌套的 for 循环一样来处理两张表的连接操作。假设有表 A 和表 B 需要进行连接,外层循环会遍历表 A 的每一行记录,对于表 A 中每一行已获取到的记录,内层循环会完整地遍历表 B 的每一行记录。在每次内层循环遍历过程中,都会根据预先设定的连接条件(比如 WHERE A.column = B.column)来判断表 A 当前行与表 B 当前行是否匹配,如果匹配,则将这两行对应的字段组合起来,形成连接后的结果行输出。

例如,有一个 “学生表”(包含学生 ID、姓名等字段)和一个 “课程表”(包含课程 ID、课程名称等字段),若要通过学生选修课程的关联关系(假设存在一个关联字段表示学生所选课程对应的课程 ID)进行连接操作,简单的嵌套循环连接算法就是先从学生表中取出第一行学生记录,然后遍历整个课程表,逐行判断课程表中的课程 ID 是否与该学生所选课程 ID 匹配,匹配的话就将学生信息和课程信息合并成一行作为结果输出;接着再取学生表的第二行记录,重复上述对课程表的遍历和匹配过程,直至学生表的所有行都处理完毕。

  1. 性能特点

  • 时间复杂度高:其时间复杂度是两张表行数的乘积,即 O(n*m),其中 n 表示表 A 的行数,m 表示表 B 的行数。这意味着随着参与连接的表数据量增大,比较操作的次数会急剧增加,性能会快速下降。例如,表 A 有 1000 行数据,表 B 有 2000 行数据,那么总共需要进行 1000 * 2000 = 2000000 次比较操作(仅为理论计算,实际情况可能因数据库实现细节稍有不同),如果数据量再大一些,计算量将非常庞大,执行时间会很长。

  • 内存使用相对简单:在执行过程中,主要是逐行读取两张表的数据进行比较,内存中不需要额外存储大量的中间数据结构(相较于一些复杂的连接算法而言),不过如果表数据量过大,频繁的磁盘 I/O 读取数据到内存进行比较也会消耗较多资源,影响整体性能。

  1. 适用场景

  • 小数据集连接:适用于参与连接的表数据量都比较小的情况,比如一些配置表、字典表之间的连接操作,由于数据量少,即使进行全量的逐行比较,性能开销也在可接受范围内,并且实现简单,易于理解和维护。

  • 对实时性要求极高且数据量不大的场景:当需要快速响应连接查询结果,且连接的数据量不大时,简单的嵌套循环连接可以快速完成操作,不需要复杂的前期准备工作(如构建索引、排序等),能满足实时性要求较高的业务需求,例如在某些小型的实时监控系统中,偶尔进行的少量数据关联查询场景。

二、索引嵌套循环连接(Index Nested Loop Join)

  1. 基本原理

同样是基于嵌套循环的思想,但与简单的嵌套循环连接不同的是,它充分利用了索引来优化内层循环的查找过程。还是以表 A 和表 B 进行连接为例,外层循环依然遍历表 A 的每一行记录,而对于表 A 中每一行记录对应的连接字段值,在内层循环中,不是去遍历整个表 B,而是利用表 B 中连接字段上建立的索引来快速定位匹配的记录。

例如,在上述学生表和课程表连接的例子中,如果在课程表的课程 ID 字段上建立了索引(假设是 B-Tree 索引),当外层循环取到学生表中的某一学生记录以及其对应的所选课程 ID 值后,通过该课程 ID 值在内层循环利用课程表的索引,可以快速定位到课程表中与之匹配的课程记录,然后将学生信息和课程信息进行连接输出结果。这样就避免了像简单的嵌套循环连接那样对表 B 进行全量的逐行遍历,大大减少了比较操作的次数。

  1. 性能特点

  • 利用索引提升效率:借助索引的快速查找特性,显著减少了内层循环中查找匹配记录的时间开销,相比于简单的嵌套循环连接,在处理大数据量连接时,如果索引设计合理且能有效利用,性能提升非常明显。特别是对于连接字段区分度高(即不同值的数量占总记录数比例大,能很好地区分不同行数据)的情况,通过索引可以快速定位到目标记录,极大地减少了不必要的比较操作。

  • 依赖索引存在开销:不过,索引本身的维护是有成本的,在数据插入、更新、删除时,数据库需要额外花费时间和资源来更新索引结构,以保证其准确性和有效性。而且,如果索引设计不合理(比如选错索引类型、索引字段顺序不当等)或者出现索引失效的情况(如对索引字段进行函数运算、隐式数据类型转换等导致不能使用索引),那么索引嵌套循环连接的性能优势将大打折扣,甚至可能比简单的嵌套循环连接还慢。

  1. 适用场景

  • 有合适索引的大数据量连接:当参与连接的表中,其中一张表的数据量较大,而另一张表在连接字段上有合适的索引(且索引能正常发挥作用)时,索引嵌套循环连接是一种很好的选择。例如,在电商系统中,要连接订单表(数据量较大)和用户表(相对订单表数据量小一些),通过用户表中的用户 ID 字段(在订单表中作为外键关联用户表,且用户 ID 字段在用户表中有索引)进行连接,就可以利用索引嵌套循环连接算法高效地完成连接操作,获取包含订单和用户详细信息的结果集。

  • 频繁基于特定字段连接的场景:如果业务中经常需要根据某个或某些特定字段进行表的连接操作,那么为这些字段建立索引并采用索引嵌套循环连接算法,能够在多次查询中持续提升性能,优化用户体验,比如在社交平台中,经常通过用户 ID 字段连接用户信息表、用户动态表、用户好友表等,为用户 ID 字段建立索引并使用该算法能快速获取相关的关联数据。

三、块嵌套循环连接(Block Nested Loop Join)

  1. 基本原理

块嵌套循环连接是对简单的嵌套循环连接的一种优化改进,旨在减少内层循环中频繁读取磁盘数据的次数,提高磁盘 I/O 的效率。它的做法是,将外层表(假设为表 A)的数据按块(通常是根据内存缓冲区的大小划分合适的数据块)读取到内存中,对于每一个读入内存的数据块,再去遍历内层表(表 B)的所有数据(可以逐行遍历,也可能结合索引等方式优化,取决于具体实现和表的情况),按照连接条件判断是否匹配,匹配的则进行连接操作输出结果。

例如,假设内存缓冲区一次能容纳 100 行表 A 的数据,那么就先将表 A 的数据分成若干个 100 行的数据块依次读入内存,对于每一个内存中的数据块,再去遍历表 B 的数据进行匹配连接,而不是像简单的嵌套循环连接那样每次只取表 A 的一行数据去遍历表 B,这样通过批量处理表 A 的数据,减少了磁盘 I/O 的次数,提高了整体效率。

  1. 性能特点

  • 优化磁盘 I/O 性能:通过以数据块为单位读取外层表数据,减少了磁盘读取操作的频率,尤其是当表数据存储在磁盘上时,能有效缓解频繁的磁盘 I/O 对性能的影响,使得在处理大数据量连接时,相比简单的嵌套循环连接,性能有一定程度的提升,更适合处理磁盘存储的大量数据之间的连接操作。

  • 内存使用与性能平衡:需要合理划分数据块大小,若数据块过大,可能导致内存缓冲区不足,出现频繁的内存交换(将内存数据临时交换到磁盘以腾出空间,会严重影响性能);若数据块过小,则无法充分发挥减少磁盘 I/O 的优势,所以要根据服务器的内存资源以及表数据的特点等因素来权衡确定合适的数据块大小,以达到内存使用和性能提升的较好平衡。

  1. 适用场景

  • 大数据量且磁盘 I/O 受限场景:当参与连接的表数据量很大,并且数据主要存储在磁盘上,磁盘 I/O 成为性能瓶颈时,块嵌套循环连接通过优化磁盘 I/O 操作,能够在一定程度上提高连接效率。比如在企业级的数据库应用中,处理海量的业务数据(如大型企业的销售数据、库存数据等多表之间的连接查询),这些数据通常存储在磁盘上,采用块嵌套循环连接算法可以更好地应对磁盘 I/O 带来的挑战,实现相对高效的连接操作。

  • 内存资源有限的情况:在服务器内存资源不是很充裕的情况下,通过合理设置数据块大小,使块嵌套循环连接能在有限的内存条件下,尽可能地完成大数据量表之间的连接,避免因内存不足导致的性能崩溃,保障系统的正常运行以及查询操作的基本性能要求。

这三种 JOIN 算法各有特点,MySQL 等数据库系统会根据参与连接的表的具体情况(如数据量大小、是否有索引、内存资源等因素),由查询优化器自动选择合适的算法来执行 JOIN 操作,以尽量优化连接性能,满足不同业务场景下的查询需求。

参考:

面试美团,被问:如何进行JOIN优化?答完直接给了35k【马士兵】_哔哩哔哩_bilibili

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

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

相关文章

uniapp使用扩展组件uni-data-select出现的问题汇总

前言 不知道大家有没有学习过我的这门课程那,《uniCloud云开发Vue3版本官方推荐用法》,这么课程已经得到了官方推荐,想要快速上手unicloud的小伙伴们,可以学习一下这么课程哦,不要忘了给一键三连呀。 在录制这门课程…

Spring 自调用事务失效分析及解决办法

前言 博主在写公司需求的时候,有一个操作涉及到多次对数据库数据的修改。当时就想着要加 Transactional注解来声名事务。并且由于一个方法中有太多行了,于是就想着修改数据库的操作单独提取出来抽象成一个方法。但这个时候,IDEA 提示我自调用…

常见的数据结构---数组、链表、栈的深入剖析

目录 一、数组(Array) 二、链表(Linked List) 三、栈(Stack) 四、总结 数据结构是算法的基石,是程序设计的核心基础。不同的数据结构适用于不同的场景和需求,选择合适的数据结构能…

KAN-Transfomer——基于新型神经网络KAN的时间序列预测

1.数据集介绍 ETT(电变压器温度):由两个小时级数据集(ETTh)和两个 15 分钟级数据集(ETTm)组成。它们中的每一个都包含 2016 年 7 月至 2018 年 7 月的七种石油和电力变压器的负载特征。 traffic(交通) :描…

【C++算法】20.二分查找算法_x 的平方根

文章目录 题目链接:题目描述:解法C 算法代码:图解 题目链接: 69. x 的平方根 题目描述: 解法 暴力解法: 如果x17 从1,2,3,4,5......这些数里面找他们的平方…

阿里云人工智能平台(PAI)免费使用教程

文章目录 注册新建实例交互式建模(DSW)注册 注册阿里云账号进行支付宝验证 新建实例 选择资源信息和环境信息,填写实例名称 资源类型需要选择公共资源,才能使用资源包进行抵扣。目前每月送250计算时。1 * NVIDIA A10 8 vCPU 30 GiB 1 * 24 GiB1 * NVIDIA V100 8 vCPU 32 Gi…

【Web】0基础学Web—html基本骨架、语义化标签、非语义化标签、列表、表格、表单

0基础学Web—html基本骨架、语义化标签、非语义化标签、列表、表格、表单 html基本骨架语义化标签图片属性a链接 非语义化标签特殊符号标签 列表无序列表结果展示 有序列表结果展示 定义列表结果展示 表格table属性tr属性结果展示 表单单标签form属性input属性selecttextareabu…

iwebsec 靶场 —— SSRF 漏洞

免责声明 本博客文章仅供教育和研究目的使用。本文中提到的所有信息和技术均基于公开来源和合法获取的知识。本文不鼓励或支持任何非法活动,包括但不限于未经授权访问计算机系统、网络或数据。 作者对于读者使用本文中的信息所导致的任何直接或间接后果不承担任何…

docker-compose 升级

官方下载地址: https://github.com/docker/compose/releases 下载完放到kali root目录下 # mv docker-compose-Linux-x86_64 /usr/local/bin/docker-compose # chmod x /usr/local/bin/docker-compose # docker-compose --version

五天SpringCloud计划——DAY1之mybatis-plus的使用

一、引言 咱也不知道为啥SpringCloud课程会先教mybatis-plus的使用,但是教都教了,就学了吧,学完之后觉得mybatis-plus中的一些方法还是很好用了,本文作为我学习mybatis-plus的总结提升,希望大家看完之后也可以熟悉myba…

系统实现屏幕横竖屏切换

需求场景 机器默认横屏或者竖屏显示 -强制横竖屏显示 实现思路 旋转 uboot logo 和内核 logo旋转 Android 桌面旋转触摸 这个很好理解: uboot 内核 开机动画都是有界面的,旋转改变方向,同时提供新的横屏或者竖屏logo旋转桌面&#xff0c…

【机器学习】机器学习的基本分类-监督学习-逻辑回归-Sigmoid 函数

Sigmoid 函数是一种常用的激活函数,尤其在神经网络和逻辑回归中扮演重要角色。它将输入的实数映射到区间 (0, 1),形状类似于字母 "S"。 1. 定义与公式 Sigmoid 函数的公式为: 特点 输出范围:(0, 1),适合用…

eBay 基于 Celeborn RESTful API 进行自动化工具集成实践

作者:王斐,ebay Hadoop 团队软件工程师,Apache Kyuubi PMC member,Apache Celeborn Committer。 简介:Apache Celeborn 是一个统一的大数据中间服务,致力于提高不同MapReduce引擎的效率和弹性。为了Spark …

Python 和 Pyecharts 对Taptap相关数据可视化分析

结果展示: 数据来源: Python爬取TapTap 热门游戏信息并存储到数据库(详细版) 目录 结果展示: 数据来源: Python爬取TapTap 热门游戏信息并存储到数据库(详细版 一、引言 二、准备工作 三、…

【Linux】常见指令 + 权限概念

文章目录 一、重要的指令mkdir指令rmdir指令 && rm 指令man指令cp指令mv指令less指令find指令tar指令 二、关于Linux中的权限文件访问者的分类(人)文件类型和访问权限(事物属性)文件权限值的表示方法文件访问权限的相关设…

老旧前端项目如何升级工程化的项目

因为历史的原因存在着大量的老旧前端项目,而在今天的开发环境中已经不再适应了,于是产生了升级到新的环境的需求。比如笔者当前的一个登录页面项目,就是以下面为技术栈的老旧项目。 基于 jQuery包管理基于 require.js,甚至有的没…

在国外,使用中国移动app办理停机保号

1.人在国内的时候,先使用手机下载中国移动app 以前网上营业厅是可以直接办理停机保号的,现在不可以了 2.人在国内的时候,确保自己的手机能够登录中国移动app 这个步骤保证回国前可以使用中国移动app复机 3.人在国内的时候,拨打…

C# 解决【托管调试助手 “ContextSwitchDeadlock“:……】问题

文章目录 一、遇到问题二、解决办法 一、遇到问题 托管调试助手 “ContextSwitchDeadlock”:“CLR 无法从 COM 上下文 0x56e81e70 转换为 COM 上下文 0x56e81d48,这种状态已持续 60 秒。拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows …

Vscode阅读C/C++ Code实用教程

目录 1.必备插件2.创建工程3.重要的快捷键及使用 1.必备插件 C/C IntelliSense - 用于跳转GitLens — Git supercharged -用于查看git 提交记录Remote - SSH -用于连接linux服务器 2.创建工程 创建工程还是蛮重要的,虽然不创建同样可以看Code,创建工程…