Sunskey

日拱一卒,不期而至

0%

Join 相关知识点

到底可不可以使用join

  • 不让使用join,使用join有什么问题?
  • 如果有两个大小不同的表做Join,应该用哪个表做驱动表?
Index Nested-Loop Join

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

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

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

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

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

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

在驱动表时走的全表扫描,而被驱动表走的树搜索。

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

假设驱动表的行数为N,执行过程就要扫描驱动表N行,对于每一行,到被驱动表上匹配一次。

近似复杂度是N + N2log2M。

显示,N对扫描行数的影响更大,因此应该让小表来做驱动表。

结论:

1.使用join语句,性能比强行拆分多个单表执行SQL语句的性能要好;

2.如果使用join语句的话,需要让小表做驱动表。

Simple Nested-Loop Join

被驱动表没有索引

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

如果继续使用算法2的话,这个算法的结果是正确的,算法叫做”Simple Nested-Loop Join”。整个扫描需要N*M行,显然效率和扫描的行数就十分的低下。

Block Nested-Loop Join

被驱动表上没有索引

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

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

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

假设小表的行数是N,大表的行数是M,那么在这个算法里:

1.两个表都做一次全表扫描,所以总的扫描行数是M+N。

2.内存中的 判断次数是M*N。

如果由于join_buffer过小,驱动表的数据行数是N,需要分K段才能完成算法流程,被驱动表的数据行数是M。

注意,这个这里的K不是常数,N越大K就会越大,因此把 K 表示为λ*N,显然λ的取值范围是 (0,1)。

这个算法的执行过程

1.扫描行数N+λNM;

2.内存判断 N*M 次。

join_buffer_size越大,一次可以放入的行越多,分成的段数也就越少,被驱动表的全表扫描次数就越少。

问题解答
  • 能不能使用join语句。

1.如果可以使用Index Nested-Loop Join算法,也就是使用被驱动表上的索引,其实没有问题。

2.如果使用Block Nesetd-Loop Join算法,扫描的行数就会过多,尤其大表上的join,这样可能要扫描被驱动表很多次,会占用大量的系统资源,所以这种join尽量不要有。

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

1.如果是Index Nested-Loop join算法,应该选择小表做驱动表。

2.如果是Block Nestd-Loop join算法

在join_buffer_size足够大的时候,是一样的。

在join_buffer_size不够大的时候,应该选择小表做驱动表。

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

join该怎么优化

Multi-Range Read(MMR)优化

这个优化的主要目的是尽量使用顺序读盘。

因为大多数数据都是按照主键递增顺序插入得到的,所以可以认为i,如果按照主键的递增顺序查询的话,对磁盘的读比交接近顺序读,能够提升读性能。

MRR优化思路

1.根据索引a,定位到满足条件的记录,将id值放入read_rnd_buffer中;

2.将read_rnd_buffer中的id进行递增排序

3.排序后的id数组,依次到主键id索引中查记录,并作为结果返回。

MRR能够提升性能的核心在于,可以得到足够多的主键id。这样通过排序以后,再去主键索引查数据,才能体现出“顺序性”的优势。

Batched Key Access

MySQL5.6以后引入这个算法,其实就是对NLJ算法的优化。

一次性从表驱动表中多拿一些行出来,一起传给被驱动表。

如果要使用BKA优化算法的话,需要执行SQL语句之前,先设置

set optimizer_switch=’mrr=on,mrr_cost_based=off,batched_key_access=on’;

BNL算法的性能问题

大表join操作虽然对IO有影响,但是语句执行结束后,对IO的影响也就结束了。但是,对Buffer Pool的影响是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率。

BNL算法对系统的影响主要包括3个方面:

1.可能会多次扫描被驱动表,占用磁盘IO资源;

2.判断join条件需要执行M*N次对比,如果是大表就会占用非常多的CPU资源;

3.可能回暖导致Buffer Pool的热数据被淘汰,影响内存命中率。

BNL转BKA

一些情况下,我们可以在被驱动表上建立索引,这是就可以直接转成BKA算法了。

创建临时表

1.把表t2中满足条件的数据放在临时表tmp_t中;

2.为了让join使用BKA算法,给临时表tmp_t的字段b加上索引;

3.让表t1和tmp_t做join操作。

总体思路,无论是在原表上加索引,还是用有索引的临时表,我们的思路都是让join语句能够用上被驱动表上的索引,来触发BKA算法,提升查询性能。

扩展 - hash join

join_buffer里面维护的不是一个无序数组,而是一个hash表的话,那么就不是10亿次判断,而是100万次hash查找。这样的话,整条语句的执行速度就会快很多。

这,也是MySQL的优化器和执行器一直被诟病的一个原因:不支持哈希join.

具体的优化思路,可以自己在业务端实现。

1.select * from t1;取得表t1的全部1000行数据,在业务端存入一个hash结构

2.select * from t2 where b> =1 and b<=2000;获取t2中满足条件的2000行数据。

3.把这2000行数据,一行一行去到业务端,到hash结构的数据表中寻找匹配的数据。满足匹配的条件的这行数据你,就作为结果集的一行。