MySQL的Join查询详解

CREATE TABLE `t1` (
 `id` int(11) NOT NULL, 
 `a` int(11) DEFAULT NULL, 
 `b` int(11) DEFAULT NULL, 
 PRIMARY KEY (`id`), KEY `a` (`a`)
 ) ENGINE=InnoDB;

t1里插入100条数据

CREATE TABLE `t2` (
 `id` int(11) NOT NULL, 
 `a` int(11) DEFAULT NULL, 
 `b` int(11) DEFAULT NULL, 
 PRIMARY KEY (`id`), KEY `a` (`a`)
 ) ENGINE=InnoDB;

t2里插入1000调数据

Index Nested-Loop Join

//straight_join让MySQL使用固定的连接方式进行查询
select * from t1 straight_join t2 on (t1.a=t2.a);

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

  • 从表 t1 中读入一行数据 R;
  • 从数据行 R 中,取出 a 字段到表 t2 里去查找;
  • 取出表 t2 中满足条件的行,跟 R 组成一行,作为结果集的一部分;
  • 重复执行上述步骤,直到表 t1 的末尾循环结束。

这种查询操作可以用上被驱动表t2上的索引,叫做 Index Nested-Loop Join。

对应流程如下:
在这里插入图片描述
复杂度: 驱动表t1做了全表扫描,共100行,被驱动表通过树搜索,由于构造的数据一一对应,所以也为扫描100行,共计扫描200行。

然后分析下如果只用单表查询,发现是查处表t1的所有数据,遍历所有数据,每一行执行查询SQL,这样看来多增加了很多次的交互,这样看来还不如Join的好

所以本质上还是可以使用Join的。

在join执行中,驱动表走全表扫描,被驱动表走树搜索。
那么假设驱动表的行数为N,被驱动表的行数为M,被驱动表搜索一行数据的复杂度为2∗log2M 2*log_2M 2log2M 驱动表的行数是N,所以整体复杂度为
N+N∗2∗log2M N+N*2*log_2M N+N2log2M整体来看,N对整体扫描行数的影响更大。

综上:前提是可以使用被驱动表的索引

  • 使用join语句比单表执行SQL性能要好
  • 使用小表做驱动表

Simple Nested-Loop Join

由于表 t2 的字段 b 上没有索引,每次到 t2 去匹配的时候,就要做一次全表扫描。

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

按照之前的数据量计算,需要扫描100*1000=10万行

MySQL最终没有采用这个方案,而采用了另外一种方案

Block Nested-Loop Join

当被驱动表上没有索引,流程如下:

  • 把表 t1 的数据读入线程内存 join_buffer 中,由于我们这个语句中写的是 select *,因此是把整个表 t1 放入了内存;
  • 扫描表 t2,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回。
    在这里插入图片描述
    在这个过程中,对表 t1 和 t2 都做了一次全表扫描,因此总的扫描行数是 1100。由于 join_buffer 是以无序数组的方式组织的,因此对表 t2 中的每一行,都要做 100 次判断,总共需要在内存中做的判断次数是:100*1000=10 万次。因为是在内存里操作,所以比Simple Nested-Loop Join这种方案好

总体复杂度为

  • 两个表都做一次全表扫描,所以总的扫描行数是 M+N;
  • 内存中的判断次数是 M*N。

当join_buffer放不下的时候,就采用分段放的方式。

  • 扫描表 t1,顺序读取数据行放入 join_buffer 中,放完第 88 行 join_buffer 满了,继续第 2 步;
  • 扫描表 t2,把 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回;
  • 清空 join_buffer;
  • 继续扫描表 t1,顺序读取最后的 12 行数据放入 join_buffer 中,继续执行第 2 步。
    在这里插入图片描述
    这个流程才体现出了这个算法名字中“Block”的由来,表示“分块去 join”。

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

所以,在这个算法的执行过程中:

  • 扫描行数是 N+λNM;
  • 内存判断 N*M 次。

显然,内存判断次数是不受选择哪个表作为驱动表影响的。而考虑到扫描行数,在 M 和 N 大小确定的情况下,N 小一些,整个算式的结果会更小。

所以让小表当驱动表。

在 N+λNM 这个式子里,λ才是影响扫描行数的关键因素,这个值越小越好。

什么参数会影响 K 的大小呢?(也就是λ的大小)答案是 join_buffer_size。join_buffer_size 越大,一次可以放入的行越多,分成的段数也就越少,对被驱动表的全表扫描次数就越少。

这就是为什么,你可能会看到一些建议告诉你,如果你的 join 语句很慢,就把 join_buffer_size 改大。

常见问题

能不能使用 join 语句?

  • 如果可以使用 Index Nested-Loop Join 算法,也就是说可以用上被驱动表上的索引,其实是没问题的;
  • 如果使用 Block Nested-Loop Join 算法,扫描行数就会过多。尤其是在大表上的 join 操作,这样可能要扫描被驱动表很多次,会占用大量的系统资源。所以这种 join 尽量不要用。

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

  • 如果是 Index Nested-Loop Join 算法,应该选择小表做驱动表;
  • 如果是 Block Nested-Loop Join 算法:
    • 在 join_buffer_size 足够大的时候,是一样的;
    • 在 join_buffer_size 不够大的时候(这种情况更常见),应该选择小表做驱动表。

总是使用小表做驱动表

小表的定义

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值