Mysql 在进行关联表查询时,通常先对一张表A进行查询,然后将循环使用查询出来的每一条记录的关联信息,再来查询另一张表B中的记录,这样查询次数为1 + COUNT(从表A查询的记录)。所以,为了提高查询效率,需要尽量将小表作为首先查询的表,我们可以关联查询时首次使用的表A为主表,将其它表如表B成为嵌套表。(EXPLAIN时,第一个出现的一定是小表即驱动表,后边出现的是大表即被驱动表)

一、IN和EXISTS查询

根据MySQL高级知识(十)——批量插入数据脚本中的相应步骤在 tb_dept_bigdata 表中插入100条数据,在 tb_emp_bigdata 表中插入5000条数据。

mysql大表无关联上个月查询合并优化 mysql大小表关联_数据

mysql大表无关联上个月查询合并优化 mysql大小表关联_mysql大表无关联上个月查询合并优化_02

注:100个部门,5000个员工。tb_dept_bigdata(小表),tb_emp_bigdata(大表)。

①当 B 表的数据集小于 A 表数据集时,即A大B小时,用 in 优于 exists

select * from tb_emp_bigdata A where A.deptno in (select B.deptno from tb_dept_bigdata B)

B表为tb_dept_bigdata:100条数据,A表tb_emp_bigdata:5000条数据。

用in的查询时间为:

mysql大表无关联上个月查询合并优化 mysql大小表关联_mysql大表无关联上个月查询合并优化_03

将上面sql转换成exists:

select *from tb_emp_bigdata A where exists(select 1 from tb_dept_bigdata B where B.deptno=A.deptno);

用exists的查询时间:

mysql大表无关联上个月查询合并优化 mysql大小表关联_数据_04

经对比可看到,在B表数据集小于A表的时候,即A大B小时,用 in 要优于 exists,当前的数据集并不大,所以查询时间相差并不多。

②当A表的数据集小于B表的数据集时,即A小B大时,用 exists 优于 in

select *from tb_dept_bigdata A where A.deptno in(select B.deptno from tb_emp_bigdata B);

用in的查询时间为:

mysql大表无关联上个月查询合并优化 mysql大小表关联_sql_05

将上面sql转换成exists:

select *from tb_dept_bigdata A where exists(select 1 from tb_emp_bigdata B where B.deptno=A.deptno);

用exists的查询时间:

mysql大表无关联上个月查询合并优化 mysql大小表关联_数据集_06

由于数据量并不是很大,因此对比并不是难么的强烈。

附上视频的结论截图:

 

mysql大表无关联上个月查询合并优化 mysql大小表关联_mysql大表无关联上个月查询合并优化_07

结论:

in 后面跟的是小表,exists 后面跟的是大表。

简记:in+小表,exists+大表。

 

二、JOIN查询

A JOIN B,表A是左表,表B示右表;左表永远是主表,右表永远是嵌套表。

Mysql在版本5.5之前,使用了Nest Loop Join算法,在版本5.5之后,采用了优化的Block Nested-Loop Join算法。优化的Block Nested-Loop Join方法,首先将外层循环数据(即驱动表查询出来的数据)的一部分缓存在 join buffer(join_buffer_size决定这个缓冲区的大小)中,然后读取被驱动表数据,逐条和buffer中的数据进行对比,从而减少了查表次数(一次Block嵌套查询,仅仅对被驱动表读取一次),这样便可以提高效率(被驱动表越大,驱动表越小,效果越明显)。而驱动表的选取,是系统根据既定规则进行选择的,理论上是选择小表作为驱动表。而 straight_join 是留给用户进行驱动表选择的利器,即使用 straight_join 时永远选择主表为驱动表,当 where 条件和 order by 不在小表时,可以尝试使用 straight_join 选择大表驱动小表。
Block Nested-Loop Join算法: https://dev.mysql.com/doc/refman/5.7/en/nested-loop-joins.html#block-nested-loop-join-algorithm

建立 t1, t2 两个完全相同的表,t1 表中写入 100 条数据,t2 表中 写入 1000 条数据。

Index Nested-Loop Join(NLJ) (被驱动表有索引的情况选择)

  - 语句

    - 为了避免Mysql 选择驱动表对于分析的影响,改用 straight_join 让 MySQL 使用固定的连接方式执行查询。

    - t1 是驱动表,t2 是被驱动表。

    - select * from t1 straight_join t2 on (t1.a=t2.a);

 

  - 执行流程

    - 在这条语句里,被驱动表 t2 的字段 a 上有索引,join 过程用上了这个索引

    - 从表 t1 中读入一行数据 R;

    - 从数据行 R 中,取出 a 字段到表 t2 里去查找;

    - 取出表 t2 中满足条件的行,跟 R 组成一行,作为结果集的一部分;

    - 重复执行步骤 1 到 3,直到表 t1 的末尾循环结束。 

    - 

mysql大表无关联上个月查询合并优化 mysql大小表关联_sql_08

 

  - 小结

    - 这个过程是先遍历表 t1,然后根据从表 t1 中取出的每行数据中的 a 值,去表 t2 中查找满足条件的记录。

    - 在形式上,这个过程很像写程序时的嵌套查询类似,并且可以用上被驱动表的索引,所以我们称之为“Index Nested-Loop Join”,简称 NLJ。 

    - 整个过程, 总扫描行数是 200(t1 100 + t2 索引树100)

 

Block Nested-Loop Join(NLJ)(被驱动表无索引时)

  - 语句

    -  select * from t1 straight_join t2 on (t1.a=t2.b);

    - 由于表 t2 的字段 b 上没有索引,因此在执行流程时,每次到 t2 去匹配的时候,就要做一次全表扫描。

 

  - 流程

    - 把表 t1 的数据读入线程内存 join_buffer 中,由于我们这个语句中写的是 select *,因此是把整个表 t1 放入了内存;

    - 扫描表 t2,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回。

    - 

mysql大表无关联上个月查询合并优化 mysql大小表关联_sql_09

 

  - 小结

    - 可以看到,在这个过程中,对表 t1 和 t2 都做了一次全表扫描,因此总的扫描行数是 100100。

    - 由于 join_buffer 是以无序数组的方式组织的,因此对表 t2 中的每一行,都要做 100 次判断,总共需要在内存中做的判断次数是:100*1000=10 万次。

    - join_buffer 的大小是由参数 join_buffer_size 设定的,默认值是 256k。如果放不下表 t1 的所有数据话,策略很简单,就是分段放。

 

五:总结

  - 能不能使用 join ?

在连接被驱动表时可以用上被驱动表上的索引,其实是没问题的;

    - 如果使用 Block Nested-Loop Join 算法,扫描行数就会过多。

      - 尤其是在大表上的 join 操作,这样可能要扫描被驱动表很多次,会占用大量的系统资源。所以这种 join 尽量不要用。

 

  - 如果要使用 join,应该选择大表做驱动表还是选择小表做驱动表?

    - 本人感觉没有区别,在考虑Index Nested-Loop Join 算法的之外,唯一有区别的就是LEFT JOIN、RIGHT JOIN和INNER JOIN的语义。建议在考虑 Index Nested-Loop Join 算法的之外,尽量少用JOIN语法。

               - 如果非要选择join,可以考虑:

                      1)如果有排序时,考虑将排序字段所在表作为驱动表;

where