在MySQL的实现中,Nested-Loop Join有3种实现的算法:

1、 Simple Nested-Loop Join:简单嵌套循环连接

2、 Block Nested-Loop Join:缓存块嵌套循环连接

3、 Index Nested-Loop Join:索引嵌套循环连接

MySQL 8.0.18版本推出了hash join的方式以替代BNLJ(缓存块嵌套循环连接)。提高非索引的join操作查询效率,这篇有关hash join并没有整理,以后会整理的!

一、原理篇

1、Simple Nested-Loop Join

比如:

SELECT *
FROM user u
LEFT JOIN class c ON u.id = c.user_id

我们来看一下当进行 join 操作时,mysql是如何工作的:

当我们进行left join连接操作时,左边的表是驱动表,右边的表是被驱动表

特点

Simple Nested-Loop Join 简单粗暴容易理解,就是通过双层循环比较数据来获得结果,但是这种算法显然太过于粗鲁,如果每个表有1万条数据,那么对数据比较的次数

=1万 * 1万 =1亿次,很显然这种查询效率会非常慢。这个全是磁盘扫描!

因为每次从驱动表取数据比较耗时,所以MySQL即使在没有索引命中的情况下也并没有采用这种算法来进行连接操作,而是下面这种!

2、Block Nested-Loop Join

同样以上面的sql为例,我们看下mysql是如何工作的

SELECT *
FROM user u
LEFT JOIN class c ON u.id = c.user_id

因为每次从驱动表取一条数据都是磁盘扫描所有比较耗时。

这里就做了优化就是每次从驱动表取一批数据放到内存中,然后对这一批数据进行匹配操作

这批数据匹配完毕,再从驱动表中取一批数据放到内存中,直到驱动表的数据全都匹配完毕。

这块内存在MySQL中有一个专有的名词,叫做 join buffer,我们可以执行如下语句查看 join buffer 的大小

show variables like '%join_buffer%'

思考,Join Buffer缓存的对象是什么,这个问题相当关键和重要。

Join Buffer存储的并不是驱动表的整行记录,具体指所有参与查询的列都会保存到Join Buffer,而不是只有Join的列。

比如下面sql

SELECT a.col3
FROM a JOIN b ON a.col1 = b.col2
WHERE a.col2 > 0 AND b.col2 = 0

上述SQL语句的驱动表是a,被驱动表是b,那么存放在Join Buffer中的列是所有参与查询的列,在这里就是(a.col1,a.col2,a.col3)。

也就是说查询的字段越少,Join Buffer可以存的记录也就越多!

变量join_buffer_size的默认值是256K,显然对于稍复杂的SQL是不够用的。好在这个是会话级别的变量,可以在执行前进行扩展。

建议在会话级别进行设置,而不是全局设置,因为很难给一个通用值去衡量。另外,这个内存是会话级别分配的,如果设置不好容易导致因无法分配内存而导致的宕机问题。

-- 调整到1M
set session join_buffer_size = 1024 * 1024 * 1024;
-- 再执行查询
SELECT a.col3
FROM a JOIN b ON a.col1 = b.col2
WHERE a.col2 > 0 AND b.col2 = 0

3、Index Nested-Loop Join

当我们了解Block Nested-Loop Join 算法,我们发现虽然可以将驱动表的数据放入Join Buffer中,但是缓存中的每条记录都要和被驱动表的所有记录都匹配一遍,

也会非常耗时,所以我们应该如何提高被驱动表匹配的效率呢?其实很简单 就是给被驱动表连接的列加上索引,这样匹配的过程就非常快,如图所示

上面图中就是先匹配索引看有没有命中的数据,有命中数据再回表查询这条记录,获取其它所需要的数据,但列的数据在索引中都能获取那都不需要回表查询,效率更高!

二、SQL示例

1、新增表和填充数据

-- 表1 a字段加索引 b字段没加
CREATE TABLE `t1` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`a` int DEFAULT NULL COMMENT '字段a',
`b` int DEFAULT NULL COMMENT '字段b',
PRIMARY KEY (`id`),
KEY `idx_a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 表2
create table t2 like t1;
-- t1插入10000条数据 t2插入100条数据
drop procedure if exists insert_data;
delimiter ;;
create procedure insert_data()
begin
declare i int;
set i = 1;
while ( i <= 10000 ) do
insert into t1(a,b) values(i,i);
set i = i + 1;
end while;
set i = 1;
while ( i <= 100) do
insert into t2(a,b) values(i,i);
set i = i + 1;
end while;
end;;
delimiter ;
call insert_data();

2、Block Nested-Loop Join算法示例

-- b字段没有索引
explain select t2.* from t1 inner join t2 on t1.b= t2.b;
-- 执行结果
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+
| 1 | SIMPLE | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 100 | 100.00 | NULL |
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 10337 | 10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+----------------------------------------------------+

从执行计划我们可以得出一些结论:

  • 驱动表是t2,被驱动表是t1。所以使用 inner join 时,排在前面的表并不一定就是驱动表。

  • Extra 中 的 Using join buffer (Block Nested Loop) 说明该关联查询使用的是 BNLJ 算法。

上面的sql大致流程是:

  1. 将 t2 的所有数据放入到 join_buffer
  2. 将 join_buffer 中的每一条数据,跟表t1中所有数据进行比较
  3. 返回满足join 条件的数据

3、Index Nested-Loop Join 算法

-- a字段有索引
EXPLAIN select * from t1 inner join t2 on t1.a= t2.a;

执行结果

从执行计划我们可以得出一些结论:

  1. 我们可以看出 t1的type不在是all而是ref,说明不在是全表扫描,而是走了idx_a的索引。

  2. 这里并没有出现 Using join buffer (Block Nested Loop) ,说明走的是Index Nested-Loop Join。

上面的sql大致流程是:

  1. 从表 t2 中读取一行数据
  2. 从第 1 步的数据中,取出关联字段 a,到表 t1 idx_a 索引中查找;
  3. 从idx_a 索引上找到满足条件的数据,如果查询数据在索引树都能找到,那就可以直接返回,否则回表查询剩余字段属性再返回。
  4. 返回满足join 条件的数据

发现这里效率最大的提升在于t1表中rows=1,也就是说因为idx_a 索引的存在,不需要把t1每条数据都遍历一遍,而是通过索引1次扫描可以认为最终只扫描 t1 表一行完整数据。

三、join优化总结

根据上面的知识点我们可以总结以下有关join优化经验:

  1. 在关联查询的时候,尽量在被驱动表的关联字段上加索引,让MySQL做join操作时尽量选择INLJ算法

2)小表做驱动表!

当使用left join时,左表是驱动表,右表是被驱动表,当使用right join时,右表是驱动表,左表是被驱动表,当使用join时,mysql会选择数据量比较小的表作为驱动表,

大表作为被驱动表,如果说我们在 join的时候明确知道哪张表是小表的时候,可以用straight_join写法固定连接驱动方式,省去mysql优化器自己判断的时间。

对于小表定义的明确

在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。

3)在适当的情况下增大 join buffer 的大小,当然这个最好是在会话级别的增大,而不是全局级别

4)不要用 * 作为查询列表,只返回需要的列!

这样做的好处可以让在相同大小的join buffer可以存更多的数据,也可以在存在索引的情况下尽可能避免回表查询数据。

声明: 公众号如需转载该篇文章,发表文章的头部一定要 告知是转至公众号: 后端元宇宙。同时也可以问本人要markdown原稿和原图片。其它情况一律禁止转载!

MySQL join语句怎么优化?的更多相关文章

  1. Mysql join语句的优化

    Mysql4.1开始支持SQL的子查询.这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中.使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的 ...

  2. 35 | join语句怎么优化?

    在上一篇文章中,我和你介绍了 join 语句的两种算法,分别是 Index Nested-Loop Join(NLJ) 和 Block Nested-Loop Join(BNL). 我们发现在使用 N ...

  3. mysql join语句的执行流程是怎么样的

    mysql join语句的执行流程是怎么样的 join语句是使用十分频繁的sql语句,同样结果的join语句,写法不同会有非常大的性能差距. select * from t1 straight_joi ...

  4. Mysql join语句解析

    1. 右连接(right join, right outer join) 解释:以右表(Sys_Employee)为准,而左表(Sys_Department)中的记录只有当其DepartmentId在 ...

  5. Mysql insert语句的优化

    1) 如果你同时从同一客户插入很多行,使用多个值表的INSERT语句.这比使用分开INSERT语句快(在一些情况中几倍).    Insert into test values(1,2),(1,3), ...

  6. MySQL join的实现原理及优化思路

    Join 的实现原理 在MySQL 中,只有一种Join 算法,也就是Nested Loop Join,没有其他很多数据库所提供的Hash Join,也没有Sort Merge Join.顾名思义,N ...

  7. 第 8 章 MySQL 数据库 Query 的优化

      前言: 在之前“影响 MySQL 应用系统性能的相关因素”一章中我们就已经分析过了Query语句对数据库性能的影响非常大,所以本章将专门针对 MySQL 的 Query 语句的优化进行相应的分析. ...

  8. MySQL Join 的实现原理

    在寻找Join 语句的优化思路之前,我们首先要理解在MySQL 中是如何来实现Join 的,只要理解了实现原理之后,优化就比较简单了.下面我们先分析一下MySQL 中Join 的实现原理.在MySQL ...

  9. MySQL 数据库 Query 的优化

    理解MySQL的Query Optimizer MySQL Optimizer是一个专门负责优化SELECT 语句的优化器模块,它主要的功能就是通过计算分析系统中收集的各种统计信息,为客户端请求的Qu ...

  10. MySQL Join算法与调优白皮书(一)

    正文 Inside君发现很少有人能够完成讲明白MySQL的Join类型与算法,网上流传着的要提升Join性能,加大变量join_buffer_size的谬论更是随处可见.当然,也有一些无知的PGer攻 ...

随机推荐

  1. linux下rsync的同步

    rsync是linux系统下的数据镜像备份工具.使用快速增量备份工具Remote Sync可以远程同步,支持本地复制,或者与其他SSH.rsync****主机同步 文件下载地址: 链接:https:/ ...

  2. el-form-item label中的字体样式设置格式

    1.设置前的代码 <el-form-item label="管理员密码" prop="password" > <el-input type=& ...

  3. Tesla-E380,4K eDP一键点屏神器问世

    eDP屏快速点亮,EDID回读, eDP屏调试 是否为点屏的准备工作感到烦躁: 1)查找LCD模组的数据手册(常常还未必能找着) 2)在上位机软件或者单片机程序里设置一大堆的LCD屏参,这个频率,那个 ...

  4. 18.-cookies和session

    一.会话定义 从打开浏览器访问一个网站,到关闭浏览器结束此次访问,称之为一次绘画 HTTP协议是无状态的,导致绘画状态难以保持 Cookies和session就是为了保持会话状态而诞生的两个存储技术 ...

  5. 在不受支持的 Mac 上安装 macOS Ventura、Monterey、Big Sur (OpenCore Legacy Patcher)

    请访问原文链接:https://sysin.org/blog/install-macos-13-on-unsupported-mac/,查看最新版.原创作品,转载请保留出处. 作者主页:www.sys ...

  6. PCA降维的原理及实现

    PCA可以将数据从原来的向量空间映射到新的空间中.由于每次选择的都是方差最大的方向,所以往往经过前几个维度的划分后,之后的数据排列都非常紧密了, 我们可以舍弃这些维度从而实现降维 原理 内积 两个向量 ...

  7. pod(八):pod的调度——将 Pod 指派给节点

    目录 一.系统环境 二.前言 三.pod的调度 3.1 pod的调度概述 3.2 pod自动调度 3.2.1 创建3个主机端口为80的pod 3.3 使用nodeName 字段指定pod运行在哪个节点 ...

  8. Java安全之动态加载字节码

    Java字节码 简单说,Java字节码就是.class后缀的文件,里面存放Java虚拟机执行的指令. 由于Java是一门跨平台的编译型语言,所以可以适用于不同平台,不同CPU的计算机,开发者只需要将自 ...

  9. 云原生之旅 - 11)基于 Kubernetes 动态伸缩 Jenkins Build Agents

    前言 上一篇文章 云原生之旅 - 10)手把手教你安装 Jenkins on Kubernetes 我们介绍了在 Kubernetes 上安装 Jenkins,本文介绍下如何设置k8s pod作为Je ...

  10. 2022春每日一题:Day 24

    题目:Work Group 树形dp,设状态f[u][0/1] 表示以u为根节点,他的子树中选了0(偶数)1(奇数)个节点的最大价值,设x为他的一个儿子,显然f[u][1]=max(f[k][0]+f ...