34 | 到底可不可以使用join?

1.  join 有什么问题

2.  两个大小不同表 join,哪个做驱动表?

34 | 到底可不可以使用join?_第1张图片

主键索引 id 和索引 a,b 上无索引。 t2 插1000 行, t1  100 行

一、Index Nested-Loop Join(NLJ)

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

straight_join 指定方式join:t1 驱动表,t2 被驱动表。

直接用 join 优化器选驱动表,影响分析执行过程。

图 1 使用索引字段 join 的 explain 结果  

1.  t1 读一行数据 R;       //全表扫描,扫描 100 行

2.  R 中a 字段到t2 里查    //树搜索,每次只扫一行,总100 行

3.  t2 满足条件行+R = 结果集一部分(一行);

4.  重复直到 t1 循环结束。

34 | 到底可不可以使用join?_第2张图片
图 2 Index Nested-Loop Join 算法的执行流程  

1.1 能不能使用 join?

不使用 join,单表查询。不如直接 join 

1. select * from t1,所有数据,100 行1句

2.从每一行 R 取出字段 a 的值 $R.a;select * from t2 where a=$R.a;100 行100

3.返回结果+R = 结果集一行。//多100 次交互: 拼接 SQL 语句和结果

1.2 怎么选择驱动表?

驱动表(行数 N):全表扫描,被驱动表(行数M):树搜索。

树搜索复杂度: 2 为底M对数,log2M,查一行是 2*log2M

驱动表:N 行到被驱动表上匹配一次。

近似复杂度N + N*2*log2M。

N 对扫描行数影响更大,小表做驱动表。

二、Simple Nested-Loop Join

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

b 上没有索引, t2 全表扫描(100次)。总共扫描 100*1000=10 万行。

t1 和 t2 都是 10 万行表,要扫描 100 亿行,太“笨重”,MySQL 没用这个算法

三、Block Nested-Loop Join 没可用索引

1.   t1读入线程内存 join_buffer 中,select *整个 t1 放内存;

2.  扫描 t2,每行取出,跟 join_buffer 对比,满足 join 条件作为结果集一部分返回。

内存操作,速度快多,性能好。时间复杂度和上面一样

34 | 到底可不可以使用join?_第3张图片
图 3 Block Nested-Loop Join 算法的执行流程  
图 4 不使用索引字段 join 的 explain 结果

3.1哪个表做驱动表

都一样

1.  总扫描行 M+N;(1100)

2.  判断次数是 M*N。(100*1000=10 万次join_buffer 无序数组)

3.2 t1 大表,join_buffer 放不下怎么办?

分段放(Block由来)。join_buffer_size 默认 256k,改成 1200,再执行:

1.  扫描表 t1,顺序读取数据行放入 join_buffer 中,放完第 88 行 join_buffer 满

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

3. 清空 join_buffer;重复上面

34 | 到底可不可以使用join?_第4张图片
图 5 Block Nested-Loop Join -- 两段  

判断等值条件的次数还是不变的,依然是(88+12)*1000=10 万次。

3.3 驱动表选择问题

小表当驱动表。驱动表 N行,分 K段完成,被驱动表M行。

K = λ*N,λ (0,1),N 越大 K 越大

1.  扫描行数N+λ*N*M;

2.  内存判断 N*M 次。

考虑扫描行数,N 小结果小。λ才是关键因素,越小越好。

N 固定,什么参数会影响 K 的大小呢?(就是λ的大小)join_buffer_size。 分段越少,扫描次数越少。

join 语句很慢,把join_buffer_size 改大

四、能不能使用 join 语句?

1.   Index Nested-Loop Join 算法,没问题;

2.  Block Nested-Loop Join 算法不要,扫描行多。尤其大表上 join 操作,扫描被驱动表很多次,会占用大量的系统资源。

explain 结果,Extra 有没有出现“Block Nested Loop”字样。

五、用 join,选大表还是小表做驱动表?

1.  Index Nested-Loop Join ,小表

2.  Block Nested-Loop Join 算法:

join_buffer_size 足够一样不够小表

六、什么叫作小表

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;

都用不上索引,第二:join_buffer 只放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 放到 join_buffer 中,只放b值(小表) t1 为驱动表。

 t2 放 id、a 和 b。

两个表按照各自条件过滤,计算参与 join 各个字段总数据量,数据量小就是小表,驱动表。

小结

能否用被驱动表索引,对 join 性能影响大

用被驱动表索引join 好;否则,不要用;

用 join 时,小表做驱动表

思考题

Block Nested-Loop Join 算法,可能会因为 join_buffer 不够大,被驱动表做多次全表扫描。

被驱动表大表(冷数据),除查询可能会IO 压力大,MySQL 服务还有什么更严重影响?(结合上一篇知识点)

两种情况:

(1)数据量小于old,驱动表分段,被驱动表多次读,大表,循环读取间隔超1秒,移到young区Buffer pool hit rate命中率极低,其他请求读磁盘响应慢,阻塞。

(2 )被驱动表数据量大于 Buffer Pool ,影响young部分更新

评论1

为什么会进入young区域?

大表t M页>old区域N页,对t进行k次全表扫描。第一次扫描时,1~N页依次被放入old区域,访问N+1页时淘汰1页,放入N+1页,以此类推,第一次扫描结束后old区域存放的是M-N+1~M页。第二次扫描开始,访问1页,淘汰M-N+1页,放入1页。可以把M页想象成一个环,N页想象成在这个环上滑动的窗口,由于M>N,不管是哪次扫描,需要访问的页都不会在滑动窗口上,所以不会存在“被访问的时候数据页在 LRU 链表中存在的时间超过了 1 秒“而被放入young的情况。我能想到的会被放入young区域的情况是,在当次扫描中,由于一页上有多行数据,需要对该页访问多次,超过了1s,不管这种情况就和t大小没关系了,而是由于page size太大,而一行数据太少。

评论2

 join_buffer 不够大,多次全表扫描,“长事务”。除了导致undo log 不能被回收回滚段空间膨胀问题,还会出现:

1. 长期占DML锁,引发DDL拿不到锁堵满连接池

 2. SQL执行socket_timeout超时后业务接口重复发起IO负载上升雪崩;

3. 实例异常,DBA kill SQL因繁杂的回滚执行时间过长,不能快速恢复

4.select *返回,网络拥堵,拖慢服务端

你可能感兴趣的:(34 | 到底可不可以使用join?)