MySQL中join查询的深入探究

目录
  • 前引
  • 索引对 join 查询的影响
    • 数据准备
    • 有索引查询过程
    • 无索引查询过程
  • 了解 Block Nested-Loop Join
    • Block Nested-Loop Join查询过程
    • Join_buffer
  • 如何正确的写出 join 查询
    • 驱动表的选择
    • 什么是小表
  • 结论:

前引

相信大家 MySQL 都用了很久了,各种 join 查询天天都在写,但是 join 查询到底是怎么查的,怎么写才是最正确的,今天我就和大家一起学习探讨一下

索引对 join 查询的影响

数据准备

假设有两张表 t1、t2,两张表都存在有主键索引 id 和索引字段 a,b 字段无索引,然后在 t1 表中插入 100 行数据,t2 表中插入 1000 行数据进行实验

CREATE TABLE `t2` (
 `id` int NOT NULL,
 `a` int DEFAULT NULL,
 `b` int DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `t2_a_index` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
​
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;
CALL **idata**();
CREATE TABLE t1 LIKE t2;
INSERT INTO t1 (SELECT * FROM t2 WHERE id <= 100);

有索引查询过程

我们使用查询 SELECT * FROM t1 STRAIGHT_JOIN t2 ON (t1.a=t2.a);因为 join 查询 MYSQL 优化器不一定能按照我们的意愿去执行,所以为了分析我们选择用 STRAIGHT_JOIN 来代替,从而更直观的进行观察

图 1

可以看出我们使用了 t1 作为驱动表,t2 作为被驱动表,上图的 explain 中显示本次查询用上了 t2 表的字段 a索引,所以这个语句的执行过程应该是下面这样的:

  • 从 t1 表中读取一行数据 r
  • 从数据 r中取出字段 a到表 t2 中进行匹配
  • 取出 t2 表中符合条件的行,和 r组成一行作为结果集的一部分
  • 重复执行步骤 1-3,直到表 t1 循环数据

该过程称之为 Index Nested-Loop Join,在这个流程里,驱动表 t1 进行了全表扫描,因为我们给 t1 表插入了 100 行数据,所以本次的扫描行数是 100,而进行 join 查询时,对于 t1 表的每一行都需去 t2 表中进行查找,走的是索引树搜索,因为我们构造的数据都是一一对应的,所以每次搜索只扫描一行,也就是 t2 表也是总共扫描 100 行,整个查询过程扫描的总行数是 100+100=200 行。

无索引查询过程

SELECT * FROM t1 STRAIGHT_JOIN t2 ON (t1.a = t2.b);

图 2

可以看出由于 t2 表字段 B上没有索引,所以按照上述 SQL 执行时每次从 t1 去匹配 t2 的时候都要做一次全表扫描,这样算下来扫描 t2 多大 100 次,总扫描次数就是 100*1000 = 10 万行。

当然了这个查询结果还是在我们建的这两个都是小表的情况下,如果是数量级 10 万行的表,就需要扫描 100 亿行,这就太恐怖了!

了解 Block Nested-Loop Join

Block Nested-Loop Join查询过程

那么被驱动表上没有存在索引,这一切都是怎么发生的呢?

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

  • 把 t1 的数据读取线程内存 join_buffer 中,因为上述我们写的是 select * from,所以相当于是把整个 t1 表放入了内存;
  • 扫描 t2 的过程,实际上是把 t2 的每一行取出来,跟 join_buffer 中的数据去做对比,满足 join 条件的,作为结果集的一部分进行返回。

所以结合图 2中 Extra 部分说明 Using join buffer 可以发现这一丝端倪,整个过程中,对表 t1 和t2 都做了一次全表扫描,因此扫描的行数是 100+1000=1100 行,因为 join_buffer 是以无序数组的方式组织的,因此对于表 t2 中每一行,都要做 100 次判断,总共需要在内存中进行的判断次数是 100*1000=10 万次,但是因为这 10 万次是发生在内存中的所以速度上要快很多,性能也更好。

Join_buffer

根据上述已经知道了,没有索引的情况下 MySQL 是将数据读取内存进行循环判断的,那么这个内存肯定不是无限制让你使用的,这时我们就需要用到一个参数 join_buffer_size,该值默认大小 256k,如下图:

SHOW VARIABLES LIKE '%join_buffer_size%';

图 4

假如查询的数据过大一次加载不完,只能够加载部分数据(80 条),那么查询的过程就变成了下面这样

  • 扫描表 t1,顺序读取数据行放入 join_buffer 中,直至加载完第 80 行满了
  • 扫描表 t2,把 t2 表中的每一行取出来跟 join_buffer 中的数据做对比,将满足条件的数据作为结果集的一部分返回
  • 清空 join_buffer
  • 继续扫描表 t1,顺序读取剩余的数据行放入 join_buffer 中,执行步骤 2

这个流程体现了算法名称中 Block 的由来,分块 join,可以看出虽然查询过程中 t1 被分成了两次放入 join_buffer 中,导致 t2 表被扫描了 2次,但是判断等值条件的次数还是不变的,依然是(80+20)*1000=10 万次。

所以这就是有时候 join 查询很慢,有些大佬会让你把 join_buffer_size 调大的原因。

如何正确的写出 join 查询

驱动表的选择

  • 有索引的情况下

在这个 join 语句执行过程中,驱动表是走全表扫描,而被驱动表是走树搜索。

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

假设驱动表的行数是 N,执行过程就要扫描驱动表 N 行,然后对于每一行,到被驱动表上 匹配一次。因此整个执行过程,近似复杂度是 N + N2log2M。显然,N 对扫描行数的影响更大,因此应该让小表来做驱动表。

  • 那没有索引的情况

上述我知道了,因为 join_buffer 因为存在限制,所以查询的过程可能存在多次加载 join_buffer,但是判断的次数都是 10 万次,这种情况下应该怎么选择?

假设,驱动表的数据行数是 N,需要分 K 段才能完成算法流程,被驱动表的数据行数是 M。这里的 K不是常数,N 越大 K就越大,因此把 K 表示为λ*N,显然λ的取值范围 是 (0,1)。

扫描的行数就变成了 N+λNM,显然内存的判断次数是不受哪个表作为驱动表而影响的,而考虑到扫描行数,在 M和 N大小确定的情况下,N 小一些,整个算是的结果会更小,所以应该让小表作为驱动表

总结:真相大白了,不管是有索引还是无索引参与 join 查询的情况下都应该是使用小表作为驱动表。

什么是小表

还是以上面表 t1 和表 t2 为例子:

SELECT * FROM t1 STRAIGHT_JOIN t2 ON t1.b = t2.b WHERE t2.id <= 50;
​
SELECT * FROM t2 STRAIGHT_JOIN t1 ON t1.b = t2.b WHERE t2.id <= 50;

上面这两条 SQL 我们加上了条件 t2.id <= 50,我们使用了字段 b,所以两条 SQL 都没有用上索引,但是第二条 SQL 可以看出 join_buffer 只需要放入前 50 行,显然查询更快,所以 t2 的前 50 行就是那个相对较小的表,也就是我们上面说所说的‘小表’。

再看另一组:

SELECT t1.b,t2.* FROM t1 STRAIGHT_JOIN t2 ON t1.b = t2.b WHERE t2.id <= 100;
​
SELECT t1.b,t2.* FROM t2 STRAIGHT_JOIN t1 ON t1.b = t2.b WHERE t2.id <= 100;

这个例子里,表 t1 和 t2 都是只有 100 行参加 join。 但是,这两条语句每次查询放入 join_buffer 中的数据是不一样的: 表 t1 只查字段 b,因此如果把 t1 放到 join_buffer 中,只需要放入字段 b 的值; 表 t2 需要查所有的字段,因此如果把表 t2 放到 join_buffer 中的话,就需要放入三个字 段 id、a 和 b。

这里,我们应该选择表 t1 作为驱动表。也就是说在这个例子里,”只需要一列参与 join 的 表 t1“是那个相对小的表。

结论:

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

到此这篇关于MySQL中join查询的文章就介绍到这了,更多相关MySQL join查询内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • mysql使用from与join两表查询的区别总结

    前言 在mysql中,多表连接查询是很常见的需求,在使用多表查询时,可以from多个表,也可以使用join连接连个表 这两种查询有什么区别?哪种查询的效率更高呢? 带着这些疑问,决定动手试试 1.先在本地的mysql上先建两个表one和two one表 CREATE TABLE `one` ( `id` int(0) NOT NULL AUTO_INCREMENT, `one` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB

  • 浅谈为什么MySQL不推荐使用子查询和join

    做分页查询: 1.对于mysql,不推荐使用子查询和join是因为本身join的效率就是硬伤,一旦数据量很大效率就很难保证,强烈推荐分别根据索引单表取数据,然后在程序里面做join,merge数据. 2.子查询就更别用了,效率太差,执行子查询时,MYSQL需要创建临时表,查询完毕后再删除这些临时表,所以,子查询的速度会受到一定的影响,这里多了一个创建和销毁临时表的过程. 3.如果是JOIN的话,它是走嵌套查询的.小表驱动大表,且通过索引字段进行关联.如果表记录比较少的话,还是OK的.大的话业务逻

  • MySQL联表查询基本操作之left-join常见的坑

    概述 对于中小体量的项目而言,联表查询是再常见不过的操作了,尤其是在做报表的时候.然而校对数据的时候,您发现坑了吗?本篇文章就 mysql 常用联表查询复现常见的坑. 基础环境 建表语句 DROP TABLE IF EXISTS `role`; CREATE TABLE `role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_name` VARCHAR(50) DEFAULT NULL COMMENT '角色名', PRIMARY KEY (`i

  • Mysql连接join查询原理知识点

    Mysql连接(join)查询 1.基本概念 将两个表的每一行,以"两两横向对接"的方式,所得到的所有行的结果. 假设: 表A有n1行,m1列: 表B有n2行,m2列: 则表A和表B"对接"之后,就会有: n1*n2行: m1+m2列. 2.则他们对接(连接)之后的结果类似这样: 3.连接查询基本形式: from  表1  [连接方式] join  表2  [on连接条件]连接查询基本形式: from  表1  [连接方式] join  表2  [on连接条件] 1

  • Mysql join连接查询的语法与示例

    连接查询: 是将两个查询(或表)的每一行,以"两两横同对接"的方式,所得到的所有行的结果,即一个表中的某行,跟另一个表中的某行.进行"横向对接",得到一个新行. 连接查询包括以下这些不同形式,连接方式: 交叉连接.内连接.外连接(分:左外连接,右外连接) 连接查询语法: select * from 表名 [连接方式] join 表名 [on 连接条件] where ...; 测试数据: mysql> select * from test; +----+----

  • 解析MySQL join查询的原理

    MySQL用Nested-Loop Join算法实现join查询 区分驱动表和被驱动表,以驱动表的结果集为循环的基础,访问被驱动表过滤数据,然后合并结果,驱动表在外循环.被驱动表在内循环.如果还有第三张参与join查询的表,则以合并的结果为驱动表,第三张表作为被驱动表,以此类推. left join中的左表是驱动表.右表是被驱动表,right join刚好相反. Nested-Loop Join有三种实现 SNLJ Simple Nested-Loop Join 假设A是驱动表,B是被驱动表.

  • MySQL优化之使用连接(join)代替子查询

    使用连接(JOIN)来代替子查询(Sub-Queries) MySQL从4.1开始支持SQL的子查询.这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中.例如,我们要将客户基本信息表中没有任何订单的客户删除掉,就可以利用子查询先从销售信息表中将所有发出订单的客户ID取出来,然后将结果传递给主查询,如下所示: DELETE FROM customerinfo WHERE CustomerID NOT in (SELECT CustomerID FR

  • MySQL中join查询的深入探究

    目录 前引 索引对 join 查询的影响 数据准备 有索引查询过程 无索引查询过程 了解 Block Nested-Loop Join Block Nested-Loop Join查询过程 Join_buffer 如何正确的写出 join 查询 驱动表的选择 什么是小表 结论: 前引 相信大家 MySQL 都用了很久了,各种 join 查询天天都在写,但是 join 查询到底是怎么查的,怎么写才是最正确的,今天我就和大家一起学习探讨一下 索引对 join 查询的影响 数据准备 假设有两张表 t1

  • Mysql中Join的使用实例详解

    在前几章节中,我们已经学会了如果在一张表中读取数据,这是相对简单的,但是在真正的应用中经常需要从多个数据表中读取数据. 本章节我们将向大家介绍如何使用MySQL 的 JOIN 在两个或多个表中查询数据. 你可以在SELECT, UPDATE 和 DELETE 语句中使用Mysql 的 join 来联合多表查询. 以下我们将演示MySQL LEFT JOIN 和 JOIN 的使用的不同之处. 在命令提示符中使用JOIN 我们在RUNOOB数据库中有两张表 tcount_tbl 和 runoob_t

  • MySQL中连接查询和子查询的问题

    目录 多表连接的基本语法 交叉连接和笛卡尔积现象 交叉连接 笛卡尔积现象 内连接 外连接 左外连接 右外连接 全外连接 子查询 多表连接的基本语法 多表连接,就是将几张表拼接为一张表,然后进行查询 select 字段1, 字段2, ... from 表1 {inner|lift|right} join 表2 on 连接条件; 有如下两张表:部门表和员工表 交叉连接和笛卡尔积现象 交叉连接 交叉连接,又名无条件内连接/笛卡尔连接 第一张表种的每一项会和另一张表的每一项依次组合 select * f

  • MySQL中JOIN连接的基本用法实例

    目录 join流程详解 一.笛卡尔积:CROSS JOIN 二.内连接:INNER JOIN 三.左连接:LEFT JOIN 四.右连接:RIGHT JOIN 五.全连接:OUTER JOIN 总结 join流程详解 join 是指 将两个表连接起来,两个表分别为 驱动表 和 被驱动表. 我们拿下面的这个sql举例, select t1.id,t2.id from t1 inner join t2 on t1.id = t2.id where t1.id > 10; t1和t2 都对 id 建立

  • MySQL中join语句怎么优化

    目录 Simple Nested-Loop Join Block Nested-Loop Join Index Nested-Loop Join 如何选择驱动表? Simple Nested-Loop Join 我们来看一下当进行 join 操作时,mysql是如何工作的.常见的 join 方式有哪些? 如图,当我们进行连接操作时,左边的表是驱动表,右边的表是被驱动表 Simple Nested-Loop Join 这种连接操作是从驱动表中取出一条记录然后逐条匹配被驱动表的记录,如果条件匹配则将

  • mySQL中in查询与exists查询的区别小结

    一.关于exists查询 explain select * from vendor where EXISTS(select * from area where area_code = vendor_prov_code ) limit 10 以上是一个典型的exists查询的sql语句. 它的作用方式是这样的:每次从vendor表中查询出一条数据,然后将这条数据中的vendor_prov_code值传递到exists查询中进行执行,也就是进行子查询的执行. 如果子查询查到的数据就返回布尔值true

  • Mysql中分页查询的两个解决方法比较

    mysql中分页查询有两种方式, 一种是使用COUNT(*)的方式,具体代码如下 复制代码 代码如下: SELECT COUNT(*) FROM foo WHERE b = 1; SELECT a FROM foo WHERE b = 1 LIMIT 100,10; 另外一种是使用SQL_CALC_FOUND_ROWS 复制代码 代码如下: SELECT SQL_CALC_FOUND_ROWS a FROM foo WHERE b = 1 LIMIT 100, 10; SELECT FOUND_

  • mysql中模糊查询的四种用法介绍

    下面介绍mysql中模糊查询的四种用法: 1,%:表示任意0个或多个字符.可匹配任意类型和长度的字符,有些情况下若是中文,请使用两个百分号(%%)表示. 比如 SELECT * FROM [user] WHERE u_name LIKE '%三%' 将会把u_name为"张三","张猫三"."三脚猫","唐三藏"等等有"三"的记录全找出来. 另外,如果需要找出u_name中既有"三"又有

  • MySql中JOIN的用法示例详解

    目录 笛卡尔积:CROSS JOIN 内连接:INNER JOIN 左连接:LEFT JOIN 右连接:RIGHT JOIN 外连接:OUTER JOIN USING子句 自然连接:NATURE JOIN 上次面试被问到JOIN,自己都已经忘了课堂上讲的笛卡尔积那些就是JOIN,最近重新复习了一遍 JOIN的含义就如英文单词“join”一样,连接两张表,大致分为内连接,外连接,右连接,左连接,自然连接. 先创建两个表,下面用于示例 CREATE TABLE t_blog( id INT PRIM

  • Mysql中使用时间查询的详细图文教程

    目录 一.使用等号查询 二.查询某月.某日.某年 三.查询时间范围 总结 一.使用等号查询 可以像普通查询使用等号进行查询,但必须查询时间必须和字段对应时间完全相等,比如我要查下面这个值 sql如下: SELECT id, start_time, end_time FROM pay_fee_rule WHERE start_time ='2022-10-9 10:33:39' 查询结果: 但只要改变其中一个值,那么就查不出来了,比如将值改为“2022-10-9 10:33:38”,查询结果如下:

随机推荐