Mysql 百问系列 : Join 会有哪几种工做方式?

前言:

做者本人从事it 行业多年,没进过什么大企业。摸爬滚打多年,多少有些经验。抗的了服务器,写的了前端。前端

熟悉的语言:PHP,JavaScript,Python 等。mysql

熟悉的框架:Laravel,Django,Scrapy,React,Vue 等。sql

数据库:Mysql,Redis,Es等。数据库

早些年其实就想写些东西,但又无处下手。直到这几年,带领团队,给组员培训,分享。才想到为何不把要培训的内容,分享的知识写成博文呢,而不是简单几页PPT。bash

除了这篇 "Mysql 百问系列" 后续还会有更多系列跟你们见面吧。文中知识点都是查阅资料,亲自实践得出来的结论,若是理解有偏误,请不吝赐教。服务器

阅读说明:

每同样 技术 或者 功能 都是为了解决某一个问题而诞生的。 搞清楚 为何 要这么作 比 搞清楚 具体怎么作 要重要。 因此我都会以问题开头,但愿你们能先想一想本身是否知道这些问题的答案,而后对比我给出的答案,若是不一致思考下到底谁的答案有问题,动手证实。 这种经过攻克一个个问题从而提高本身的能力,就是我但愿经过一篇篇文章给你们带来的价值。app

每篇文章 我会聚焦于一两个点,尽可能不扩散太多。固然技术类文章没法避免一个知识点牵连另外一个知识点。这种碰到遇到不懂知识点的时候我建议你们知道有这么个东西,把精力集中于当前问题,而不要太扩散。固然后续我也会根据反馈,进行填坑。框架

问题

  1. 什么是Index Nested-Loop Join?
  2. 什么是Block Nested-Loop Join?
  3. join buffer 作什么用的?
  4. 工做当中到底应不该该用join ?


准备工做

CREATE TABLE `school` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `school_id` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_school` (`school_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

delimiter;;
CREATE PROCEDURE idata ( ) BEGIN
    DECLARE
        i INT;
    
    SET i = 1;
    WHILE
            ( i <= 1000 ) DO
            INSERT INTO school
        VALUES
            ( i, 'school' );
        SET i = i + 1;
        
    END WHILE;
    
END;;

delimiter;
CALL idata ( );


drop procedure idata;
delimiter;;
CREATE PROCEDURE idata ( ) BEGIN
    DECLARE
        i INT;
    
    SET i = 1;
    WHILE
            ( i <= 1000 ) DO
            INSERT INTO student
        VALUES
            ( i, i%2+1, 'stu');
        SET i = i + 1;
        
    END WHILE;
    
END;;

delimiter;
CALL idata ( );复制代码

如今有两张表,一张 shool表,一张 student表。 各插入1000条数据oop

Index Nexted-Loop Join

EXPLAIN select * from student STRAIGHT_JOIN school on school.id = student.school_id复制代码

(使用STRAIGHT_JOIN) 是防止Mysql 优化器自动优化,具体优化逻辑此处不展开,后续有机会详聊。优化

执行结果:

image.png

能够看到student 为 驱动表, school 为 被驱动表.

执行流程:

  1. 首先对student 作了全表扫表,这里扫描了1000行。
  2. 循环遍历这1000行, 拿一行的 school_id 去 表 school 中查找对应的数据。找到匹配的数据 ,放入结果集中。 因为school 的id 为主键,因此使用主键索引可以迅速定位到那一行。
  3. 循环遍历结束,输出结果集到客户端。

上面使用的是join 也就是内链接,若是换成Left join 或者 Right join 外链接有什么区别呢?区别就在于上面第二个步骤中,内链接须要找到匹配的数据放到结果集中, 而外链接是无论有没有找到数据,都须要把驱动表的那行数据加入到结果集中。

那么无论内链接仍是外链接, 这种拿到驱动表数据 再去循环遍历查询被驱动表,而且被驱动表查询过程当中使用到了索引的方式称为 Index Nested-Loop Join 。(从英文其实也能够看出意思了, Index 索引,Nested-loop 嵌套循环)



Block Nested-Loop Join

那么若是 被驱动表上的搜索的字段没有索引呢?

EXPLAIN select * from student STRAIGHT_JOIN  school on school.name = student.name复制代码

(虽然现实场景中不会存在 用学校名称 字段关联学生名称这种状况,这里只是说明若是 学校的name 这个没有索引的字段被拿来作关联条件时的状况)

image.png

能够看到 school 由于没法用到索引,因此必需要进行全表扫表。

那么咱们猜想的执行流程是:

  1. 扫描student 表,取出1000行。
  2. 对取出的1000行数据进行循环遍历,每一行都 去school 表中 进行搜索,因为name 字段没有索引,因此要找到对应的数据须要扫描school全表。 找到数据,放入结果集中。
  3. 输出结果集。

这个过程 咱们能够看到 须要扫描的行数为 1000 * 1000 。 若是school 或者student 数量再大必定,1万的学校,几百万的学生,那么这个查询的效率可想而知会很是低。

因此针对这种被驱动表没法使用索引的join,Mysql 作出了优化,并非咱们上面所猜想的流程。


join buffer

首先,mysql 引入了join buffer 的概念。这个join buffer的大小是能够经过启动参数或者系统变量join_buffer_size进行配置,默认大小是256K,最小能够设置为128字节。

因此真实的流程为:

  1. 扫描驱动表student,取出1000行 放入join buffer。
  2. 扫描被驱动表school, 取出其中一行, 与 join_buffer 中的数据作对比, 若是知足了join 的条件,那么加入到结果集中。 对比完一行取下一行,直到全表扫描结束。

因此上面的情形是 school 中的数据,一行行的取出来 去与 join_buffer 中的数据作对比,查找到 符合条件的结果。那么 须要扫描的行数是 school 的1000 行, 每一行又要与join_buffer 中的1000行进行对比。

这种状况 须要扫描的行数为 student 的1000行,用来放入join_buffer , 和 school 的1000行。也就是扫描了2000行。 单内存中进行判断的次数是 1000 * 1000;

能够看到经过引入join buffer 将 磁盘的扫描 变成了内存的判断,以此来提高效率


若是驱动表数据很大,致使join buffer 放不下呢? 例如 student 有 10000万行,join buffer 每次只能放1000行。

那么流程就会变为:

1.扫描 student ,直到join buffer 放满。

2.扫描 school ,取出其中一行, 与 join_buffer 中的数据作对比, 若是知足了join 的条件,那么加入到结果集中。

  1. school 扫描结束后,清空join buffer。
  2. 继续扫描student 剩下的, 循环 2,3 步骤。直到student 所有被扫描结束。

这种按 一块块 进行读取放入join buffer 并比较的方式 就是Block Nested-Loop Join 了。

优化方式

能够看到若是 join 的时候使用了Block Nested-Loop Join,那么它的执行效率与join buffer 的大小相关,join buffer 越大,分块的次数越小, 效率越高。

固然充分利用join buffer 空间也是一种方式, 放入join buffer 空间的数据是 驱动表的 查询字段 和 搜索字段。因此避免使用 * ,列清楚须要查询的字段也有利于 join buffer 空间的利用。

固然最理想的优化方式是添加索引,把 Block Nested-Loop Join 转为 Index Nested-Loop Join

索引也不是随便添加,若是业务场景中不多拿这个字段来进行搜索,单单为了某个join 而添加索引也得不偿失,还须要根据具体业务来考量。


结语

根据join 过程当中 被驱动表 能不能用到索引 将查询方式分为 Index Nested-Loop JoinBlock Nested-Loop Join

  • Index Nested-Loop Join 的效率高,能够在业务中使用。
  • Block Nested-Loop Join 虽然mysql 引入join buffer 来进行优化,可是效率仍是偏低,应该尽可能避免。

ps: 我的认为在实际业务场景中即便多个表格进行join 过程当中都使用到了Index Nested-Loop Join 的方式,可是因为每次join 都是进行笛卡尔积的过程,若是其中一张或多张表随着业务发展数据量增长,会致使 整个sql 查询效率也不是很理想。 因此仍是尽可能避免多表join 的场景。最多容许2 到 3个表格进行join 。


喜欢,支持请关注个人我的公众号

qrcode_for_gh_9e5fcea888f2_430.jpg

相关文章
相关标签/搜索