美文网首页
MySQL - join

MySQL - join

作者: kyo1992 | 来源:发表于2021-04-23 10:03 被阅读0次

    join使用问题

    • DBA 不让使用 join,使用 join 有什么问题呢;
    • 如果有两个大小不同的表做 join,应该用哪个表做驱动表呢。

    实验环境

    
    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;
    
    drop procedure idata;
    delimiter ;;
    create procedure idata()
    begin
      declare i int;
      set i=1;
      while(i<=1000)do
        insert into t2 values(i, i, i);
        set i=i+1;
      end while;
    end;;
    delimiter ;
    call idata();
    
    create table t1 like t2;
    insert into t1 (select * from t2 where id<=100)
    

    创建表t1和t2,结构相同,其中t1有100条数据,t2有1000条。

    Index Nested-Loop Join - 索引嵌套循环join

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

    在这个语句里,t1 是驱动表,t2 是被驱动表。
    explain结果

    可以看到,在这条语句里,被驱动表 t2 的字段 a 上有索引,join 过程用上了这个索引,因此这个语句的执行流程是这样的:

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

    这个过程是先遍历表 t1,然后根据从表 t1 中取出的每行数据中的 a 值,去表 t2 中查找满足条件的记录。在形式上,这个过程就跟我们写程序时的嵌套查询类似,并且可以用上被驱动表的索引,所以我们称之为“Index Nested-Loop Join”,简称 NLJ。
    流程图如下:


    在这个流程里:

    1. 对驱动表 t1 做了全表扫描,这个过程需要扫描 100 行;
    2. 而对于每一行 R,根据 a 字段去表 t2 查找,走的是树搜索过程。由于我们构造的数据都是一一对应的,因此每次的搜索过程都只扫描一行,也是总共扫描 100 行;
    3. 所以,整个执行流程,总扫描行数是 200。

    如果不用join,即只能使用单表查询,需要先从t1表中查询所有数据,100行,然后遍历这100行数据,每次用1条SQL在t2表中查询,也是扫描了 200 行,但是总共执行了 101 条语句,比直接 join 多了 100 次交互。除此之外,客户端还要自己拼接 SQL 语句和结果,没有直接用join好。

    怎么选择驱动表

    在这个 join 语句执行过程中,驱动表是走全表扫描,而被驱动表是走树搜索。

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

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

    因此整个执行过程,近似复杂度是 N + N2log2M。

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

    • 如果你没觉得这个影响有那么“显然”, 可以这么理解:N 扩大 1000 倍的话,扫描行数就会扩大 1000 倍;而 M 扩大 1000 倍,扫描行数扩大不到 10 倍。

    到这里小结一下,通过上面的分析我们得到了两个结论:

    1. 使用 join 语句,性能比强行拆成多个单表执行 SQL 语句的性能要好;
    2. 如果使用 join 语句的话,需要让小表做驱动表。但是,你需要注意,这个结论的前提是“可以使用被驱动表的索引”。

    Block Nested-Loop Join - 分块嵌套循环join

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

    被驱动表上没有可用的索引,算法的流程是这样的:

    1. 把表 t1 的数据读入线程内存 join_buffer 中,由于我们这个语句中写的是 select *,因此是把整个表 t1 放入了内存;
    2. 扫描表 t2,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回。
      流程图如下


    SQL explain结果


    当join buffer不够放下所有t1表数据时,流程图变为


    1. 扫描表 t1,顺序读取数据行放入 join_buffer 中,放完第 88 行 join_buffer 满了,继续第 2 步;
    2. 扫描表 t2,把 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回;
    3. 清空 join_buffer;
    4. 继续扫描表 t1,顺序读取最后的 12 行数据放入 join_buffer 中,继续执行第 2 步。

    所以,为了让更多数据能加载到join buffer,要选用小表做驱动表。
    在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。
    不能使用被驱动表的索引,只能使用 Block Nested-Loop Join 算法,这样的语句就尽量不要使用。

    相关文章

      网友评论

          本文标题:MySQL - join

          本文链接:https://www.haomeiwen.com/subject/aujklltx.html