MySQL join原理及优化

news2024/11/20 16:36:27

MySQL的JOIN原理是基于索引和算法的。在执行JOIN查询时,MySQL会根据连接字段上的索引来查找匹配的记录。
这种算法在链接查询的时候,驱动表会根据关联字段的索引进行查找,当在索引上找到了符合的值,再回表进行查询,也就是只有当匹配到索引以后才会进行回表。

在进行JOIN查询时,MySQL还采用了一些优化策略来提高查询性能,例如使用嵌套循环连接算法(Nested-Loop Join)和索引优化技术。

嵌套循环连接算法按照指定的连接方式执行查询,不会自己选择驱动表。当连接字段上有索引时,MySQL会使用索引来加速查找过程

Join 算法

使用 left join 时,左边的表不一定是驱动表,优化器可能会将语句优化为join。如果需要 left join 的语义,就不能把被驱动表的字段放在 where 条件里面做等值判断或不等值判断,必须都写在 on 里面

为了便于量化分析各种Join 算法,以下创建两个表 t1 和 t2 来说明

CREATE TABLE `t2` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
) ENGINE=InnoDB;
 
drop procedure idata;
delimiter ;;
create procedure idata()
begin
  declare i int;
  set i=1;
  while(i<=1000)do
    insert into t2 values(i, i, i);
    set i=i+1;
  end while;
end;;
delimiter ;
call idata();
 
create table t1 like t2;
insert into t1 (select * from t2 where id<=100)

可以看到,这两个表都有一个主键索引 id 和一个索引 a,字段 b 上无索引。存储过程 idata() 往表 t2 里插入了 1000 行数据,在表 t1 里插入的是 100 行数据

Index Nested-Loop Join

select * from t1 straight_join t2 on (t1.a=t2.a);

为了便于分析执行过程中的性能问题,我改用straight_join让 MySQL 使用固定的连接方式执行查询,这样优化器只会按照我们指定的方式去 join。在这个语句里,t1 是驱动表,t2 是被驱动表

如果直接使用 join 语句,MySQL 优化器可能会选择表 t1 或 t2 作为驱动表,这样会影响我们分析 SQL 语句的执行过程

INL算法步骤为先遍历表 t1,然后根据从表 t1 中取出的每行数据中的 a 值,去表 t2 中查找满足条件的记录。在形式上,这个过程就跟我们写程序时的嵌套查询类似,并且可以用上被驱动表的索引

Index Nested-Loop Join 算法的执行流程

查询复杂度

在INL算法程中,驱动表是走全表扫描,而被驱动表是走树搜索

假设被驱动表的行数是 M。每次在被驱动表查一行数据,要先搜索索引 a,再搜索主键索引。每次搜索一棵树近似复杂度是以 2 为底的 M 的对数,记为 l o g 2 M log_2M log2M,所以在被驱动表上查一行的时间复杂度是 2 ∗ l o g 2 M 2 * log_2M 2log2M

假设驱动表的行数是 N,执行过程就要扫描驱动表 N 行,然后对于每一行,到被驱动表上匹配一次。

因此整个执行过程,近似复杂度是 N + N ∗ 2 ∗ l o g 2 M N + N* 2*log_2M N+N2log2M

Simple Nested-Loop Join

select * from t1 straight_join t2 on (t1.a=t2.b);

若把SQL 语句改成这样,由于表 t2 的字段 b 上没有索引,因此再用上图的执行流程时,每次到 t2 去匹配的时候,就要做一次全表扫描。复杂度是 M * N。这个 SQL 请求就要扫描表 t2 多达 100 次,总共扫描 100*1000=10 万行

MySQL 没有使用 Simple Nested-Loop Join 算法,而是使用了另一个叫作“Block Nested-Loop Join”的算法,简称 BNL

Block Nested-Loop Join

join_buffer是一个用于存储连接操作(join)中临时数据的缓冲区。当执行连接操作时,MySQL将从连接的表中读取数据,并临时存储在join_buffer中,以便执行连接操作的计算和比较

当被驱动表上没有可用的索引,算法的流程是这样的

  1. 把表 t1 的数据读入线程内存 join_buffer 中,由于我们这个语句中写的是select *,因此是把整个表 t1 放入了内存;
  2. 扫描表 t2,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 JOIN 条件的,作为结果集的一部分返回

Block Nested-LOOP JOIN 算法的执行流程

可以看到,在这个过程中,对表 t1 和 t2 都做了一次全表扫描,因此总的扫描行数是 1100。由于 join_buffer 是以无序数组的方式组织的,因此对表 t2 中的每一行,都要做 100 次判断,总共需要在内存中做的判断次数是:100*1000=10 万次

join_buffer 的大小是由参数 join_buffer_size 设定的,默认值是 256k。 如果放不下表 t1 的所有数据话,策略很简单,就是分块放

假设,驱动表的数据行数是 N,需要分 K 段才能完成算法流程,被驱动表的数据行数是 M。

注意,这里的 K 不是常数,N 越大 K 就会越大,因此把 K 表示为λ*N,显然λ的取值范围是 (0,1)。

所以,在这个算法的执行过程中:

  1. 扫描行数是 N+λNM;
  2. 内存判断 N*M 次
SNL与BNL对比

SNL/BNL 算法对系统的影响主要包括三个方面:

  1. 可能会多次扫描被驱动表,占用磁盘 IO 资源;
  2. 判断 join 条件需要执行 M*N 次对比(M、N 分别是两张表的行数),如果是大表就会占用非常多的 CPU 资源;
  3. 可能会导致 Buffer Pool 的热数据被淘汰,影响内存命中率

大表 join 操作虽然对 IO 有影响,但是在语句执行结束后,对 IO 的影响也就结束了。但是,对 Buffer Pool 的影响就是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率。

为了减少这种影响,可以考虑增大join_buffer_size的值,减少对被驱动表的扫描次数

BNL 算法的执行逻辑是:将驱动表的数据全部读入内存 join_buffer 中,然后将连接操作划分为多个块,每个块包含一定数量的记录。每一行数据都跟 join_buffer 中的数据进行匹配,匹配成功则作为结果集的一部分返回。
SNL 算法的执行逻辑是:顺序取出驱动表中的每一行数据,到被驱动表去做全表扫描匹配,匹配成功则作为结果集的一部分返回

BNL算法在处理连接操作时采用了块状处理和索引优化技术(转为BKA),使得它在处理大规模数据时能够比SNL算法更快地完成查询操作

Batched Key Access

理解了 MRR 性能提升的原理,我们就能理解 MySQL 在 5.6 版本后开始引入的 Batched KEY Access(BKA) 算法了。这个 BKA 算法,其实就是对 NLJ 算法的优化

join_buffer 在 BNL 算法里的作用,是暂存驱动表的数据。在 NLJ 算法复用 join_buffer ,就优化为BKA 算法了

Batched KEY Access 流程

图中在 join_buffer 中放入的数据是 R1~R100,表示的是只会取查询需要的字段。当然,如果 JOIN buffer 放不下 R1~R100 的所有数据,就会把这 100 行数据分成多段执行上图的流程

使用 BKA 优化算法,需要在执行 SQL 语句之前,先设置

set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

其中,前两个参数的作用是要启用 MRR。这么做的原因是,BKA 算法的优化要依赖于 MRR

Join 优化

Multi-Range Read 优化

Multi-Range Read优化的目的就是为了减少磁盘的随机访问,并且将随机访问转化为较为顺序的数据访问,这对于IO-bound类型的SQL查询语句可带来性能极大的提升。Multi-Range Read优化可适用于rangerefeq_ref类型的查询

MRR优化的优点及工作方式详见 MRR优化

如果随着 a 的值递增顺序查询的话,id 的值就变成随机的,那么就会出现随机访问,性能相对较差。而通过MRR优化后,会将满足条件的记录id值放入read_rnd_buffer中,再讲id进行递增排序后依次查记录并返回结果。执行流程如下图所示

因为大多数的数据都是按照主键递增顺序插入得到的,所以我们可以认为,如果按照主键的递增顺序查询的话,对磁盘的读比较接近顺序读,能够提升读性能

MRR优化后的执行流程

MRR优化后的explain 结果

BNL 转 BKA

select * from t1 join t2 on (t1.b=t2.b) where t2.b>=1 and t2.b<=2000;

对于表 t2 的每一行,判断 JOIN 是否满足的时候,都需要遍历 join_buffer 中的所有行。因此判断等值条件的次数是 1000*100 万 =10 亿次,这个判断的工作量很大

对于这种不适合在被驱动表上建索引的情况,可以考虑使用临时表

大致思路是:

  1. 把表 t2 中满足条件的数据放在临时表 tmp_t 中;
  2. 为了让 JOIN 使用 BKA 算法,给临时表 tmp_t 的字段 b 加上索引;
  3. 让表 t1 和 tmp_t 做 JOIN 操作
create temporary table temp_t(id int primary key, a int, b int, index(b))engine=innodb;
insert into temp_t select * from t2 where b>=1 and b<=2000;
select * from t1 join temp_t on (t1.b=temp_t.b);

总体来看,不论是在原表上加索引,还是用有索引的临时表,我们的思路都是让 JOIN 语句能够用上被驱动表上的索引,来触发 BKA 算法,提升查询性能

Hash join

业务多次查询,再到hash结构的数据表中寻找匹配的数据

对于上面计算 10 亿次那个操作,看上去有点儿傻。如果 join_buffer 里面维护的不是一个无序数组,而是一个哈希表的话,那么就不是 10 亿次判断,而是 100 万次 HASH 查找

然而 MySQL 的优化器和执行器一直被诟病的一个原因:不支持哈希 join。所以将两个表的数据分别查询,在业务中组合匹配的效率其实更高

流程大致如下:

  1. select * from t1;取得表 t1 的全部 1000 行数据,在业务端存入一个 HASH 结构,比如 C++ 里的 set、PHP 的数组这样的数据结构。
  2. select * from t2 where b>=1 and b<=2000; 获取表 t2 中满足条件的 2000 行数据。
  3. 把这 2000 行数据,一行一行地取到业务端,到 HASH 结构的数据表中寻找匹配的数据。满足匹配的条件的这行数据,就作为结果集的一行。

总结

  • .两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表
  • 如果可以使用被驱动表的索引,join 语句还是有其优势的
  • BKA 优化是 MySQL 已经内置支持的,建议默认使用
  • BNL 算法效率低,建议你都尽量转成 BKA 算法。优化的方向就是给被驱动表的关联字段加上索引
  • 基于临时表的改进方案,对于能够提前过滤出小数据的 join 语句来说,效果还是很好的
  • MySQL 目前的版本还不支持 hash join,但你可以配合应用端自己模拟出来,理论上效果要好于临时表的方案

参考资料:

  1. MySQL 四十五讲——到底可不可以使用join?
  2. MySQL 四十五讲——join语句怎么优化
  3. MySQL 四十五讲——join的写法
  4. MRR优化

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

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

相关文章

SharePoint 页面中插入自定义代码

我们都知道 SharePoint 是对页面进行编辑的。 对于一些有编程基础的人来说&#xff0c;可能需要对页面中插入代码&#xff0c;这样才能更好的对页面进行配置。 但是在新版本的 SharePoint modern 页面来说&#xff0c;虽然我们可以插入 Embed 组件。 但是 Embed 组件中是不允…

(一)正点原子I.MX6ULL kernel6.1移植准备

一、概述 学完了正点原子的I.MX6ULL移植&#xff0c;正点原子的教程是基于Ubuntu18&#xff0c;使用的是4.1.15的内核&#xff0c;很多年前的了。NXP官方也发布了新的6.1的内核&#xff0c;以及2022.04的uboot。 本文分享一下基于Ubuntu22.04&#xff08;6.2.0-36-generic&…

【C++】C++入门详解 II【深入浅出 C++入门 这一篇文章就够了】

C入门 七、引用&#xff08;一&#xff09;引用 概念&#xff08;1&#xff09;引用 概念&#xff08;2&#xff09;引用 使用★☆&#xff08;3&#xff09;引用 特性&#xff08;4&#xff09;常引用 &#xff08;二&#xff09;引用的 实际应用 及 其意义☆&#xff08;1&am…

【Nginx】nginx | 微信小程序验证域名配置

【Nginx】nginx | 微信小程序验证域名配置 一、说明二、域名管理 一、说明 小程序需要添加头条的功能&#xff0c;内容涉及到富文本内容显示图片资源存储在minio中&#xff0c;域名访问。微信小程序需要验证才能显示。 二、域名管理 服务器是阿里云&#xff0c;用的宝塔管理…

Word 插入的 Visio 图片显示为{EMBED Visio.Drawing.11} 解决方案

World中&#xff0c;如果我们插入了Visio图还用了Endnote&#xff0c; 就可能出现&#xff1a;{EMBED Visio.Drawing.11}问题 解决方案&#xff1a; 1.在相应的文字上右击&#xff0c;在出现的快捷菜单中单击“切换域代码”&#xff0c;一个一个的修复。 2.在菜单工具–>…

探索项目管理软件的多重用途和益处

项目管理软件俨然成了当下项目管理话题中的热门词条&#xff0c;作为一个辅助性管理工具&#xff0c;项目管理软件有什么用&#xff1f;真的值得购入吗&#xff1f; 什么是项目管理软件 顾名思义&#xff0c;项目管理软件就是指在项目管理过程使用的各种软件工具。项目管理软件…

spring-cloud-stream

系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 第七章 Spring Cloud 之 GateWay 第八章 Sprin…

【MongoDB】索引 – 文本索引(用权重控制搜索结果)

一、准备工作 这里准备一些数据 db.books.drop();db.books.insert({_id: 1, name: "Java", alias: "java 入门", description: "入门图书" }); db.books.insert({_id: 2, name: "C", alias: "c", description: "C 入…

【算法专题】双指针—三数之和

力扣题目链接&#xff1a;三数之和 一、题目解析 二、算法原理 解法一&#xff1a;排序暴力枚举利用set去重 代码就不写了&#xff0c;你们可以试着写一下 解法二&#xff1a;排序双指针 这题和上一篇文章的两数字和方法类似 排序固定一个数a在这个数的后面区间&#xff0…

2023年第十六届山东省职业院校技能大赛高职组“信息安全管理与评估”赛项规程

第十六届山东省职业院校技能大赛 高职组“信息安全管理与评估”赛项规程 一、赛项名称 赛项名称&#xff1a;信息安全管理与评估 英文名称&#xff1a;Information Security Management and Evaluation 赛项组别&#xff1a;高职组 赛项归属&#xff1a;电子与信息大类 二…

《詩經别解》——國風·周南·雎鳩​​​​​​​

一、关于古文的一个认识 目前可以阅读的古文经典&#xff0c;大多是经历了几千年的传承。期间的武力战争、文化纷争、宗教侵袭、官僚介入及文人的私人恩怨与流派桎梏&#xff0c;印刷与制作技术&#xff0c;导致这些古文全部都已经面目全非。简单地说&#xff0c;你读到的都是…

Java 简单实现一个 TCP 回显服务器

文章目录 TCP 服务端TCP 客户端实现效果TCP 服务端(实现字典功能)总结 TCP 服务端 package network;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Soc…

路径规划-车辆分配及导航

1.根据城市之间的连通状态&#xff0c;构建以城市为结点、两个城市间的距离&#xff08;根据两个城市经纬度计算的欧式距离&#xff09;作为边权重的无向图。 2.根据起始点&#xff0c;对除了起始点之外的其他点进行聚类&#xff0c;将点划分成几个部分。 3.在每个部分中找出…

javaSE学习笔记(七)IO流

目录 六、IO流 1.IO流概述 概念 分类 IO体系 简单介绍 最重要&#xff0c;最常用&#xff0c;最常见的两个流 2.File类 路径分隔符 绝对路径和相对路径 构造方法 方法 重命名注意事项 删除注意事项 3.FileInputStream&FileOutputStream FileInputStream 获取…

归并外排序实现

文章目录 1. 海量数据排序 1. 海量数据排序 如果我们想在文件中海量数据排序&#xff0c;我们比较适合选用归并排序。 首先&#xff0c;我们要看要排序的文件的大小&#xff0c;比如说这个文件是10G&#xff0c;而我们的内存是1G&#xff0c;那么我们可以把文件切成10份。这样…

怎么制作安装电子版说明书?方法献上~

在现代科技发展的背景下&#xff0c;制作一份优质的电子版说明书对于帮助用户正确、高效地使用产品至关重要。无论是软件、设备还是家电产品&#xff0c;一份清晰明了的电子版说明书可以为用户提供指导和支持&#xff0c;提升用户体验和满意度。那么&#xff0c;如何制作一份出…

Dubbo快速入门

1.什么是Dubbo&#xff1f; Dubbo是一款高性能分布式服务框架&#xff0c;由阿里巴巴开发并开源发布。它支持多种协议&#xff0c;如dubbo、HTTP、Hessian、Thrift等&#xff0c;可以很好地解决分布式服务中的服务治理问题&#xff0c;提供了服务注册、发现、负载均衡、容错等功…

模拟实现string类——【C++】

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; &#x1f354;前言&#xff1a; 我们已经将STL中的string类重要接口全部认识并熟练掌握&#xff0c;为了让我们对string与C类与对象更深层次的了解&#xff0c;我们这篇博客将string类进行模拟实现。 目录 string类的…

原生微信小程序学习之旅(一) -来简单的使用

文章目录 取消导航栏标头组件创建添加Component组件接收传入的数据 页面创建(Page)关于tabBartabBar自定义样式 轮播图轮播图指示点样式改变 微信小程序快速获取用户信息路由跳转获取url路径中的参数 bindtap(click)传参wx:if编写用户登陆关于默认工程目前的获取方法尝试一下服…

海外媒体发稿:彭博社发稿宣传中,5种精准营销方式

在如今的信息发生爆炸时期&#xff0c;营销方式多种多样&#xff0c;但是充分体现精准营销并针对不同用户群体的需求并非易事。下面我们就根据彭博社发稿营销推广为例子&#xff0c;给大家介绍怎样根据不同用户人群方案策划5种精准营销方式。 1.界定总体目标用户人群在制订精准…