在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. 栈溢出漏洞利用流程——以syncbrs为例

    0x1 缓冲区溢出漏洞攻击简介 缓冲区溢出攻击是针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序的堆栈,使程序转而执行其他指令,以达到攻击 ...

  2. PageRank原理分析

    pagerank是将众多网页看成一个有向图,每个页面就是有向图中的节点.计算每个节点的出度和入度.如果一个网站被大量其他的网页引用,那么他就会有更高的pr分数. 原理 对于所有与节点i相连的节点,用他 ...

  3. ubuntu下Mysql安装与root密码重置

    一.安装 1.首先更新本地存储库索引,执行sudo apt update 2.从APT存储库安装MySQL,执行sudo apt install MySQL-server,在安装过程中,可能会出现[Y ...

  4. node 创建服务器方法

    方法一 let http = require('http') let httpserver = http.createServer(function(req,res){ res.writeHead(2 ...

  5. JVM学习笔记——类加载和字节码技术篇

    JVM学习笔记--类加载和字节码技术篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的类加载和字节码技术部分 我们会分为以下几部分进行介绍: 类文件结构 字节码指令 编译期处理 类 ...

  6. 7 款殿堂级的开源 CMS(内容管理系统)

    最近,有读者留言让我推荐开源 CMS.我本想直接回复 WordPress,但是转念一想我玩 WordPress 是 2010 年左右的事情了,都过去十年了,它会不会有些过时呢?有没有新的.更好玩的开源 ...

  7. 操作系统课程设计pintos project1实验摘记

    第一部分 项目概述 一.Pintos简介 Pintos是一个基于80x86架构的简单操作系统框架,它支持内核级线程.能够加载和运行用户程序,也拥有文件系统,不过,这些功能均以一种简单的形式实现. 二. ...

  8. hwlog---api.go

    // Copyright(c) 2021. Huawei Technologies Co.,Ltd. All rights reserved.// Package hwlog provides the ...

  9. halcon如何识别硬币?

    halcon如何识别硬币? 前言 最近一直在学习halcon,在此做了一个案例,分享给大家,效果图如下: 1.思路分析 通过观察,发现1元,5角,1角,它们在面值的文字描述不一样,硬币显示的花纹不一样 ...

  10. 重要内置函数、常见内置函数、可迭代对象、迭代器对象、for循环的本质、异常捕获处理

    重要内置函数 #zip拉链 zip 函数是可以接收多个可迭代对象,然后把每个可迭代对象中的第i个元素组合在一起,形成一个新的迭代器,类型为元组. l1 = [11, 22, 33] l2 = ['a' ...