join算法分析

对于单条语句,explain看下key,加个索引mysql

多个条件,加复合索引算法

where a = ? order by b 加(a,b)的复合索引sql

上面都是比较基本的,这篇咱们分析一些复杂的状况——join的算法数据库

以下两张表作join并发

10w            100w
tb R             tb S
  r1               s1
  r2               s2
  r3               s3
  ...               ...
  rN              sN

Ⅰ、nested_loop join

1.一、simple nested_loop join 这个在数据库中永远不会使用

For each row r in R do
    For each row s in S do
        If r and s satisfy the join condition
            Then output the tuple <r,s>


扫描成本 O(Rn*Sn),也就是笛卡儿积oracle

1.二、index nested_loop join 最推荐

For each row r in R do
    lookup in S index
        if found s == row
            Then output the tuple<r,s>

扫描成本 O(Rn)oop

优化器倾向于使用小表作驱动表,内表上建立索引便可性能

select * from a,b where a.x=b.y优化

a中的每条记录,去b上面的index(y)中去找这个x的记录,a表和b表,哪一个是R,哪一个是S线程

R:驱动表(外表) S:内表

对于inner join,a b 和 b a join出来结果同样,用的索引来查询,100w和1000w扫描成本都同样,都是外表的行数,因此只要驱动表越小,优化器就倾向于用它作驱动表

对于left join,左表要全表扫描全部记录,因此必定是驱动表

好比,一列10w行,另外一列100w行,优化器会喜欢把10w的这一列作外表,而后在100w的列上建索引,外表只要扫描10w次,假设100w记录索引B+ tree高度是3,那一共就是10w * 3次io操做,反过来,就是100w * 3次,因此,mysql只要两张表关联,很是建议两张表上必定要有索引,索引应该建立在S表上,也就是大表,若是索引加反了,这时候100w的表就成了驱动表

可是线上确定不会直接两张表关联,where后面还有不少过滤条件,优化器会把这些条件考虑进去,过滤掉这些条件,看哪张表示小表就是驱动表,可是索引有倾斜,优化器在选择上可能会出错

1.3 block nested_loop join 两张表上没有索引的时候才会使用的算法

用来优化simle nested_loop join,减小内部表的扫描次数

For each tuple r in R do
    store used columns as p from R in join buffer
        For each tuple s in S do
            if p and s satisfy the join condition
                Then output the tuple <p,s>


加一个内存,join_buffer_size变量,空间换时间,这个变量决定了Join Buffer的大小

Join Buffer可被用于联接是ALL,index,range的类型

Join Buffer只存储须要进行查询操做的相关列数据(要关联的列),而不是整行的记录(千万要记住),因此总的来讲仍是蛮高效的,

扫描成本呢?和simple同样

Table R join buffer Table S
将多条R中的记录一会儿放到join buffer中,join buffer 一次性和Table S中每条记录进行比较,这样来减小内表的扫描次数,假设join buffer足够大,大到能把驱动表中全部记录cache起来,那这样就只要扫一次内表

举例:

A    B
1    1
2    2
3    3
     4
外  内

                 SNLP     BNLP
外表扫描次数       1         1
内表扫描次数       3         1
比较次数          12        12

综上:比较次数是节省不下来的

若是是百万级别,mysql勉强能够跑出来结果,调优的话,不谈索引,咱们就要调大join_buffer_size这个参数,默认256k,若是这个列是8个字节,256k显然存不下太多记录,这个参数能够调很大,可是不要过度了,否则机器内存不够,数据库挂了就很差了,通常来讲1g最大了

join_buffer_size是私有的,每一个线程能够用的内存大小

网上有个特别错误的观点,mysql加速join查询,加大join_buffer_size。这个是两张表关联没有索引用这个方法才有用,你有了索引,再怎么加大这个参数也没用

自己也不太建议用这个算法,除非某些特殊的查询用不到索引,或者当时没建索引,这是个临时处理方法,比较次数是笛卡儿积啊 个人天那,如何把这个比较次数降下来呢?

tips:
一、不知道哪一个表是驱动表,那就简单点,两个关联的表的相关列都加索引,让优化器本身选择

二、oracle中百万级别的表进行关联,用index nested_loop join,千万级以上用hash join这个说法怎么看?心法口诀是什么

三、join若是有索引,为何千万级别的join不行?这句话是错的,是能够的,只是速度很慢罢了,主要就是由于要回表

select max(l_extendedprice) from orders,lineitem
where o_orderdate betweent '1995-01-01' and '1995-01-31' and
l_orderkey=o_orderkey;

join的列不是主键,这种类型的查询就是基于索引的,join不太适用的场景

看一个问题

(root@localhost) [dbt3]> explain select * from orders where o_orderdate > '1997-01-01';
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | orders | NULL       | ALL  | i_o_orderdate | NULL | NULL    | NULL | 1483643 |    50.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

(root@localhost) [dbt3]> explain select * from orders where o_orderdate > '1999-01-01';
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys | key           | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | orders | NULL       | range | i_o_orderdate | i_o_orderdate | 4       | NULL |    1 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

为何第一条sql没走索引第二条走了呢?

这不是优化器不稳定,这个索引是一个二级索引,如今select * 因此要回表,回表对于咱们这个sql来讲,假设经过索引定位到回表的记录一共是50w条(表一共是150w条记录,每条记录大小是100字节),那就要回表50w次,大约是50w次io(看B+ tree 高度,这里假设高度就是1)

若是不走索引,直接扫150w行主键一共须要150w/(16k/100)次io(每行大小100字节,一个页就是16k/100条记录,150w除以这个值,就是一共的记录数),到这里看基本上就是1w次io,这就是优化器没走索引的缘由,并且,回表用的io是随机的,扫主键用的是顺序io,后者比前者最起码快十倍

另外,这个选择确定是基于cost的,但不要追究cost,官方没有说明cost是怎么计算的,经过源码能够看出一点,可是5.7版本又不同了

那针对这种状况咱们应该如何优化呢?——MRR(5.7以后才有,和oracle同样)

(root@localhost) [dbt3]> explain select /* +MRR(orders) */ * from orders where o_orderdate > '1998-01-01';
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+--------+----------+----------------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key           | key_len | ref  | rows   | filtered | Extra                            |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+--------+----------+----------------------------------+
|  1 | SIMPLE      | orders | NULL       | range | i_o_orderdate | i_o_orderdate | 4       | NULL | 317654 |   100.00 | Using index condition; Using MRR |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+--------+----------+----------------------------------+
1 row in set, 1 warning (0.22 sec)

MRR:Multi-Range-Read:多范围的read,空间换时间,解决回表的性能问题

随机io回表,不能保证key和pk都是顺序的,对于二级索引来讲它的key是排序了,可是不能保证里面保存的pk也排序了

因此如今弄了个内存,把这个二级索引里面的pk都放进去,等放不下了,去sort一把,而后再去回表,这时候io就比较顺序了,这样经过随机转顺序作了个优化

Ⅱ、classic hash join

把外表中的数据放到一个内存中,放进去以后不是直接去和内表比较,对内存中的列建立了一个hash表,而后再扫描内表,内表中的每条记录去探测hash表,一般查一个记录只要探测一次

A    B
1    1
2    2
3    3
     4

外表扫1次,内表扫1次,比较次数是4(S表去hash表中探测),走索引的话也是1 1 4,看起来哈希和索引看起来成本同样,hash不用建立索引,不用回表

hash join 就不存在回表的问题,还能够用来作并发,若是join要回表,用基于索引的方式作join算法效率比较差,对于oracle的话,hash join就会好很是多

hash join有个缺点是只支持等值的查询,A.x=B.y >= 这种就歇菜了,不过一般join也是等值查询

据说8.0会支持

Ⅲ、batched key access join(5.6开始支持)

用来解决若是关联的列是二级索引,对要回表的列先cache起来,排序,再回表,性能会比较好一点

bka join调的是mrr接口,可是如今这个算法有bug,默认是永远不启动的,要启用得写hint,这是mysql如今比较大的问题

上面讲了一堆mysql的不是,join算法的痤,上面这些多少w行join,线上业务会用到这些吗?咱们线上都是简单查询,这些都是很复杂的查询了,233333

错误作法:有人说业务层作join,这样不会比在数据库层作快

相关文章
相关标签/搜索