MySQL join语句怎么优化?
在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大致流程是:
- 将 t2 的所有数据放入到
join_buffer中 - 将 join_buffer 中的每一条数据,跟表t1中所有数据进行比较
- 返回满足join 条件的数据
3、Index Nested-Loop Join 算法
-- a字段有索引
EXPLAIN select * from t1 inner join t2 on t1.a= t2.a;
执行结果

从执行计划我们可以得出一些结论:
我们可以看出 t1的type不在是all而是ref,说明不在是全表扫描,而是走了idx_a的索引。
这里并没有出现 Using join buffer (Block Nested Loop) ,说明走的是Index Nested-Loop Join。
上面的sql大致流程是:
- 从表 t2 中读取一行数据
- 从第 1 步的数据中,取出关联字段 a,到表 t1 idx_a 索引中查找;
- 从idx_a 索引上找到满足条件的数据,如果查询数据在索引树都能找到,那就可以直接返回,否则回表查询剩余字段属性再返回。
- 返回满足join 条件的数据
发现这里效率最大的提升在于t1表中rows=1,也就是说因为idx_a 索引的存在,不需要把t1每条数据都遍历一遍,而是通过索引1次扫描可以认为最终只扫描 t1 表一行完整数据。
三、join优化总结
根据上面的知识点我们可以总结以下有关join优化经验:
在关联查询的时候,尽量在被驱动表的关联字段上加索引,让MySQL做join操作时尽量选择INLJ算法。
2)小表做驱动表!
当使用left join时,左表是驱动表,右表是被驱动表,当使用right join时,右表是驱动表,左表是被驱动表,当使用join时,mysql会选择数据量比较小的表作为驱动表,
大表作为被驱动表,如果说我们在 join的时候明确知道哪张表是小表的时候,可以用straight_join写法固定连接驱动方式,省去mysql优化器自己判断的时间。
对于小表定义的明确:
在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。
3)在适当的情况下增大 join buffer 的大小,当然这个最好是在会话级别的增大,而不是全局级别。
4)不要用 * 作为查询列表,只返回需要的列!
这样做的好处可以让在相同大小的join buffer可以存更多的数据,也可以在存在索引的情况下尽可能避免回表查询数据。
声明: 公众号如需转载该篇文章,发表文章的头部一定要 告知是转至公众号: 后端元宇宙。同时也可以问本人要markdown原稿和原图片。其它情况一律禁止转载!
MySQL join语句怎么优化?的更多相关文章
- Mysql join语句的优化
Mysql4.1开始支持SQL的子查询.这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中.使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的 ...
- 35 | join语句怎么优化?
在上一篇文章中,我和你介绍了 join 语句的两种算法,分别是 Index Nested-Loop Join(NLJ) 和 Block Nested-Loop Join(BNL). 我们发现在使用 N ...
- mysql join语句的执行流程是怎么样的
mysql join语句的执行流程是怎么样的 join语句是使用十分频繁的sql语句,同样结果的join语句,写法不同会有非常大的性能差距. select * from t1 straight_joi ...
- Mysql join语句解析
1. 右连接(right join, right outer join) 解释:以右表(Sys_Employee)为准,而左表(Sys_Department)中的记录只有当其DepartmentId在 ...
- Mysql insert语句的优化
1) 如果你同时从同一客户插入很多行,使用多个值表的INSERT语句.这比使用分开INSERT语句快(在一些情况中几倍). Insert into test values(1,2),(1,3), ...
- MySQL join的实现原理及优化思路
Join 的实现原理 在MySQL 中,只有一种Join 算法,也就是Nested Loop Join,没有其他很多数据库所提供的Hash Join,也没有Sort Merge Join.顾名思义,N ...
- 第 8 章 MySQL 数据库 Query 的优化
前言: 在之前“影响 MySQL 应用系统性能的相关因素”一章中我们就已经分析过了Query语句对数据库性能的影响非常大,所以本章将专门针对 MySQL 的 Query 语句的优化进行相应的分析. ...
- MySQL Join 的实现原理
在寻找Join 语句的优化思路之前,我们首先要理解在MySQL 中是如何来实现Join 的,只要理解了实现原理之后,优化就比较简单了.下面我们先分析一下MySQL 中Join 的实现原理.在MySQL ...
- MySQL 数据库 Query 的优化
理解MySQL的Query Optimizer MySQL Optimizer是一个专门负责优化SELECT 语句的优化器模块,它主要的功能就是通过计算分析系统中收集的各种统计信息,为客户端请求的Qu ...
- MySQL Join算法与调优白皮书(一)
正文 Inside君发现很少有人能够完成讲明白MySQL的Join类型与算法,网上流传着的要提升Join性能,加大变量join_buffer_size的谬论更是随处可见.当然,也有一些无知的PGer攻 ...
随机推荐
- 【做题笔记】CSP-S 往年试题
题单 本文章正在持续更新-- [2021] 廊桥分配 题目 题面描述 所有飞机分为两类--国内区和国际区,两区廊桥数量互不干扰.每架飞机遵循"先到先得"的原则,优先选择编号最小的廊 ...
- 通过 Github Action 实现定时推送天气预报
偶然间,看到 GitHub Actions 教程:定时发送天气邮件 - 阮一峰的网络日志 这篇文章,没错,这个正好能打发自己的折腾之心,也能通过代码给生活引入一些变化. 还是在这里简单记录一下实现过程 ...
- Vue学习之--------组件的基本使用(非单文件组件)(代码实现)(2022/7/22)
文章目录 1.为啥要使用组件 2.基本使用 3.代码实例 4.测试效果 5.注意点 1.为啥要使用组件 好用啊.像堆积木一样 2.基本使用 Vue中使用组件的三大步骤: 一.定义组件(创建组件) 二. ...
- 中国制霸生成器「GitHub 热点速览 v.22.42」
火遍推特的中国制霸生成器本周一开源就占据了两天的 GitHub Trending 榜,不知道你的足迹遍布了多少个省份呢?同样记录痕迹的 kanal 用了内存读写方式解决了 Rust 的消息处理问题,P ...
- NLP之Bi-LSTM(在长句中预测下一个单词)
Bi-LSTM @ 目录 Bi-LSTM 1.理论 1.1 基本模型 1.2 Bi-LSTM的特点 2.实验 2.1 实验步骤 2.2 实验模型 1.理论 1.1 基本模型 Bi-LSTM模型分为2个 ...
- 微信小程序——悬浮按钮
关键: position: fixed; wxml: <navigator url="/pages/issue/index"><image class='i ...
- MyBatis&Spring Framrwork
1. MyBatis 1.1 概述 MyBatis是一款优秀的持久层框架,用于简化JDBC开发 MyBatis本是Apache的一个开源项目iBatis,2010年这个项目迁移到了google cod ...
- 常用Python库整理
记录工作和学习中遇到和使用过的Python库. Target 四个Level 整理 Collect 学习 Learn 练习 Practice 掌握 Master 1. Python原生和功能增强 1. ...
- python基础(三)装饰器
字典推导式: data_list = ['1 hello','2 world'] result = {item.split(" ")[0]: item.split(" & ...
- dd格式化硬盘
1. dd硬盘读写测速(不做实际读写): 测试纯写入性能 dd if=/dev/zero of=/dev/sdb bs=1M count=10240 oflag=direct 测试纯读取性能 dd i ...