• IceGeek17
    2019-02-18
    节后补课,有几个问题:

    问题一:
    对于BKA算法的流程理解,用文中的例子,先把t1表(小表)中查询需要的字段放入join_buffer, 然后把join_buffer里的字段值批量传给t2表,先根据索引a查到id,然后得到一批主键id,再根据主键id排序,然后再根据排完序的id去主键索引查数据(这里用到MRR)
    理解是否正确?
    这里对于主键id排序是在哪里做的,是在join_buffer里,还是另外再开辟一块临时内存?如果在join_buffer里,那join_buffer里的每行内容是不是:t2.id + t1查询必须的字段,并且join_buffer里是根据id排序的?

    问题二:
    虽然MySQL官方没有支持hash join,但是之前看到文章说,MariaDB已经支持hash join,能不能后续在答疑文章中简单总结下mariaDB支持的join算法

    问题三:
    在实际项目中,一个比较困惑的问题,看到过这样的类似写法:
    select xxx from t1 join t2 on t1.id = t2.id for update (目的是获取几个表上最新的数据,并且加上锁,防止数据被更新)
    这里有几个问题:
    1) 像这样 join + for update,表上的加锁规则是怎么样的?是不是在需要join的两个表上根据具体的查询执行过程都加上锁?
    2)像这样 join + for update 的用法是否合理?碰到这样的场景,应该怎么去做?

    问题四:
    看过阿里输出的开发手册里,强调 “最多不超过三表join”,实际项目中,给我感觉很难做到所有业务都不超过三表join,那这里的问题就是,有什么相关的经验方法,可以尽量降低参与join的数据表?
    比如,在数据表里添加冗余字段,可以降低参与join的数据表数量,还有什么其他好的方法?


    展开
    
     22
  • 郭健
    2019-02-07
    老师,有几个问题还需要请教一下:
    1.上一章t1表100条数据,t21000条数据,mysql会每次都会准确的找出哪张表是合理的驱动表吗?还是需要人为的添加straight_join。
    2.像left join这种,左边一定是驱动表吧?以左边为标准查看右边有符合的条件,拼成一条数据,看到你给其他同学的评论说可能不是,这有些疑惑。
    3.在做join的时候,有些条件是可以放在on中也可以放在where中,比如(t1.yn=1 和t2.yn=1)这种简单判断是否删除的。最主要的是,需要根据两个条件才能join的(productCode和custCode),需要两个都在on里,还是一个在on中,一个在where中更好呢?

    作者回复: 1. 正常是会自己找到合理的,但是用前explain是好习惯哈
    2. 这个问题的展开我放到答疑文章中哈
    3. 这也是好问题,需要分析是使用哪种算法,也放到答疑文章展开哈。

    新年快乐~

     1
     12
  • 天王
    2019-02-13
    join语句的优化,NLJ算法的优化,MRR优化器会在join_buffer进行主键的排序,然后去主键索引树上一个个的查找,因为按照主键顺序去主键索引树上查找,性能会比较高,MRR优化接近顺序读,性能会比较高。BKA算法是对NLJ算法的优化,一次取出一批数据的字段到join_buffer中,然后批量join,性能会比较好。BKA算法依赖于MRR,因为批量join找到被驱动表的非聚集索引字段通过MRR去查找行数据

    作者回复: 👍

    
     8
  • 老杨同志
    2019-02-01
    我准备给
    t1增加索引c
    t2增加组合索引b,c
    t3增加组合索引b,c
    select * from t1 straight_join t2 on(t1.a=t2.a) straight_join t3 on (t2.b=t3.b) where t1.c>=X and t2.c>=Y and t3.c>=Z;

    另外我还有个问题,开篇提到的这句sql select * from t1 where a>=1 and a<=100;
    a是索引列,如果这句索引有order by a,不使用MRR 优化,查询出来就是按a排序的,使用了mrr优化,是不是要额外排序

    展开

    作者回复: 对,好问题,用了order by就不用MRR了

    
     8
  • asdf100
    2019-02-01
    最近遇到这个需求,in里面的值个数有5万左右,出现的情况很少但存在,这种情况怎么处理。?手动创建临时表再join?

    另外in内的值用不用手动排序?

    作者回复: 不需要手动排序

    不过5万个值太凶残了,语句太长不太好

    这种就是手动创建内存临时表,建上hash索引,填入数据,然后join

     2
     7
  • Mr.Strive.Z.H.L
    2019-02-02
    老师您好,新年快乐~~

    关于三表join有一个疑惑点需要确认:

    老师您在评论中说到,三表join不会是前两个表join后得到结果集,再和第三张表join。
    针对这句话,我的理解是:
    假设我们不考虑BKA,就按照一行行数据来判断的话,流程应该如下(我会将server端和innodb端分的很清楚):
    表是t1 ,t2 ,t3。 t1 straight_join t2 straight_join t3,这样的join顺序。
    1. 调用innodb接口,从t1中取一行数据,数据返回到server端。
    2. 调用innodb接口,从t2中取满足条件的数据,数据返回到server端。
    3. 调用innodb接口,从t3中取满足条件的数据,数据返回到server端。
    上面三步之后,驱动表 t1的一条数据就处理完了,接下来重复上述过程。
    (如果采用BKA进行优化,可以理解为不是一行行数据的提取,而是一个范围内数据的提取)。

    按照我上面的描述,确实没有前两表先join得结果集,然后再join第三张表的过程。
    不知道我上面的描述的流程对不对?(我个人觉得,将innodb的处理和server端的处理分隔清晰,对于sql语句的理解,会透彻很多)
    展开

    作者回复: 新年快乐,分析得很好。

    可以再补充一句,会更好理解你说的这个过程 :
      如果采用BKA进行优化,每多一个join,就多一个join_buffer

    
     6
  • xxxl
    2019-02-01
    刚刚凌乱了的那个问题,经explain验证,explain SELECT a.* FROM sys_xxtx a JOIN baq_ryxx r ON a.ryid = r.ID WHERE a.dwbh = '7E0A13A14101D0A8E0430A0F23BCD0A8' ORDER BY txsj DESC LIMIT 0,20;
    使用的索引是txsj ;
    explain SELECT a.* FROM sys_xxtx a JOIN baq_ryxx r ON a.ryid = r.ID WHERE a.dwbh = '7E0A13A14101D0A8E0430A0F23BCD0A8' ORDER BY txsj DESC LIMIT 5000,20;使用的索引是dwbh ;
    然后回忆起了第10张:MySQL为什么有时候会选错索引?
    但是从扫描行数、是否使用排序等来看在 LIMIT 5000,20时候也应该优选txsj ?可是这个时候选择的索引是dwbh, 查询时间也大大缩短
    展开

    作者回复: 嗯,这个跟我们第十篇那个例子挺像的

    我们把limit 1 改成limit 100的时候,MySQL认为,要扫描到“100行那么多”,
    你这里是limit 5000,200, 这个5000会让优化器认为,选txsj会要扫“很多行,可能很久”

    这个确实是优化器还不够完善的地方,有时候不得不用force index~

    
     5
  • 涛哥哥
    2019-03-20
    老师,对于现在的固态硬盘,这样类似顺序读写的数据库优化,不就不起作用了啊?

    作者回复: 固态硬盘的顺序写还是比随机写快的

    
     4
  • 纸片人
    2019-06-11
    知识点总结
    1、翻译嵌套查询过程
    Join = 嵌套查询
    示例:t1 join t2 on t1.a=t2.a where t1.id>=X and t2.id>=Y
    已知:驱动表t1的规模是N,被驱动表t2的规模是M
    伪代码:
    SNLJ: 扫描次数NxM, 计算次数NxM
    for r1 in t1 /* read from ~~disk~~ InnoDB(storage engine) */
      for r2 in t2
        if r1.a=r2.a
          if r1.id>=X and r2.id>=Y
            add r1+r2 into result_set
      end
    end


    BNLJ: 扫描次数N+KxM,计算次数NxM,K = Nxrow_size/join_buffer_size+1
    for r1 in t1
      add r1 into join_buffer
    end
    for r2 in t2
      for r1 in join_buffer /* read from ~~memory~~ join_buffer which is controlled by Server */
        if r1.a=r2.a
          if r1.id>=X and r2.id>=Y
            add r1+r2 into result_set
      end
    end


    INLJ: 扫描次数<N+N,计算次数N + Nx2xlnM/ln2
    for r1 in t1
      locate r1.a on t2 with index a /* search B+Tree */
      if r1.id>=X and r2.id>=Y
        add r1+r2 into result_set
    end



    2、Join优化思路
    MRR优化思想:回表前,按主键排序,执行读取操作,以保证顺序读。
    BKA算法:先按BNLJ的思想批量扫描驱动表数据,再将之按被驱动表上的索引排序,取值。
    示例:t1 join t2 on t1.a=t2.b where t1.id>=X and t2.id>=Y
    伪代码:
    for r1 in t1
      add r1 into join_buffer
    end
    sort join_buffer by r1.a
    for r1 in sort_buffer
      locate r1.a on t2 with index b /* search B+Tree */
      if r1.id>=X and r2.id>=Y
        add r1+r2 into result_set
    end


    3、BNLJ转BKA
    方案一:在被驱动表的join字段上添加索引,相当于BNLJ先转INLJ再转BKA。
    方案二:不适宜添加索引的情况(查询语句使用频率较低),引入临时表。具体操作步骤如下:
       a. 先根据where过滤被驱动表t2,并将结果存入临时表tmp_t;
       b. 在临时表上为join字段b添加索引;
       c. 让驱动表t1连接临时表tmp_t。
    (注意,由于步骤b中需要为临时表创建索引,所以此方案当且仅当tmp_t规模远小于t2时才划算!)


    4、扩展hash-join
    可视为BNLJ进阶,将join_buffer变成Hash表,处理流程如下:

    hash-join:
    for r1 in t1
      add <key:r1.a,value:r1> into join_buffer
    end
    for r2.a in t2
      locate r2.a in t1 with hash index /* read from join_buffer */
      if r1.id>=X and r2.id>=Y
        add r1+r2 into result_set
    end


    分析:
       a. 驱动表在内环,以降低内存占用率。
       b. 如果t1的尺寸大于join_buffer,那么我们就不得不多次全表扫描t2了。因为过滤条件的逻辑运算符号是and,所以还有优化的余地,可将驱动表的过滤提前,来降低t1的大小。
    for r1 in t1
      if r1.id>=X
        add <key:r1.a,value:r1> into join_buffer
    end
    for r2.a in t2
      locate r2.a in t1 with hash index /* read from join_buffer */
      if r2.id>=Y
        add r1+r2 into result_set
    end
    展开
    
     3
  • HuaMax
    2019-02-02
    前提假设:t1.c>=X可以让t1成为小表。同时打开BKA和MRR。
    1、t1表加(c,a)索引。理由:A、t1.c>=X可以使用索引;B、加上a的联合索引,join buffer里放入的是索引(c,a)而不是去主键表取整行,用于与表t2的t1.a = t2.a的join查询,不过返回SELECT * 最终还是需要回表。
    2、t2表加(a,b,c)索引。理由:A、加上a避免与t1表join查询的BNL;B、理由同【1-B】;C、加上c不用回表判断t2.c>=Y的筛选条件
    3、t3表加(b,c)索引。理由:A、避免与t2表join查询的BNL;C、理由同【2-C】

    问题:
    1、【1-B】和【2-B】由于select *要返回所有列数据,不敢肯定join buffer里是回表的整行数据还是索引(c,a)的数据,需要老师解答一下;不过值得警惕的是,返回的数据列对sql的执行策略有非常大的影响。
    2、在有join查询时,被驱动表是先做join连接查询,还是先筛选数据再从筛选后的临时表做join连接?这将影响上述的理由【2-C】和【3-C】

    使用straight_join强制指定驱动表,我会改写成这样:select * from t2 STRAIGHT_JOIN t1 on(t1.a=t2.a) STRAIGHT_JOIN t3 on (t2.b=t3.b) where t1.c>=X and t2.c>=Y and t3.c>=Z;
    考虑因素包括:
    1、驱动表使用过滤条件筛选后的数据量,使其成为小表,上面的改写也是基于t2是小表
    2、因为t2是跟t1,t3都有关联查询的,这样的话我猜测对t1,t3的查询是不是可以并行执行,而如果使用t1,t3作为主表的话,是否会先跟t2生成中间表,是个串行的过程?
    3、需要给t1加(a,c)索引,给t2加(c,a,b)索引。
    展开

    作者回复: 👍 很深入的思考哈
    1. select * ,所以放整行;你说得对,select * 不是好习惯;
    2. 第一次join后就筛选;第二次join再筛选;
    新春快乐~

    
     3
  • Destroy、
    2019-02-01
    BNL 算法效率低,建议你都尽量转成 BKA 算法。优化的方向就是给驱动表的关联字段加上索引;
    老师最后总结的时候,这句话后面那句,应该是给被驱动表的关联字段加上索引吧。

    作者回复: 对的,👍细致

    已经发起勘误,谢谢你哦,新年快乐

    
     3
  • xxxl
    2019-02-01
    order by cjsj desc limit 0,20 explain Extra只是显示 Using where ,执行时间 7秒钟
    order by cjsj desc limit 5000,20 explain Extra只是显示 Using index condition; Using where; Using filesort, 执行时间 0.1 秒
    有些许的凌乱了@^^@

    作者回复: 这正常的,一种可能是这样的:
       Using where 就是顺序扫,但是这个上要扫很久才能扫到满足条件的20个记录;
       虽然有filesort,但是如果参与排序的行数少,可能速度就更快,而且limit 有堆排序优化哦

    
     3
  • jane_pop
    2019-07-17
    老师,我想请教下,在inner join on后面加过滤条件,与呀where后面加过滤条件,性能是一样的吗?
    
     2
  • 唯她命
    2019-04-09
    存储过程 插入100万
    Query OK, 1 row affected (1 hour 53 min 57.59 sec)
    这么恐怖😱!
     2
     2
  • 憶海拾貝
    2019-02-13
    节后开工宜补课.

    按照文中说明的MRR设计思路, 是否可以反推出: 被驱动表使用非递增主键(比如UUID作为主键),就没有必要开启MRR?

    作者回复: 如果是非随机的主键,确实没必要了😅

    优化第一步还是应该把主键处理一下

    
     2
  • Mr.Strive.Z.H.L
    2019-02-13
    老师你好,今天在回顾这篇文章做总结的时候,突然有一个疑惑:

    我们假设t2的b上面有索引,该语句是左连接

    select * from t1 left join t2 on (t1.b=t2.b) where t2.b>=1 and t2.b<=2000;

    和

    select * from t1 left join t2 on (t1.b=t2.b) and t2.b>=1 and t2.b<=2000;

    到底在内部执行流程上到底有什么区别??
    因为实际工作中左连接用得挺多的。
    (这篇文章都是直连,所以使用on和where最后的结果都一样,但是左连接就不是了)
    展开

    作者回复: 你这两个语句是一样的。。是不是第二个语句多了left?

    left join因为语义上要求所有左边表的数据行都必须存在结果里面,所以执行流程不太一样,我在答疑文章中说哈

    
     2
  • 天王
    2019-02-13
    BNL算法优化,BNL算法,如果读取的是冷表,而且量比较大,循环读取,第一次数据会进入old区域,如果一秒之后没有访问,不会移到LRU头部,大表join对io影响查询完就结束了,但是buffer pool需要大量的查询把冷数据冲掉。BNL算法有3个问题,1 多次扫描被驱动表,占用磁盘io 2 判断join会耗费大量的cpu资源 3 会热数据淘汰,影响buffer pool的命中率

    作者回复: 👍

    
     2
  • WL
    2019-02-11
    请教老师两个问题:
    1. 通过主键索引找到的数据会会不会先在内存中查询, 如果没有再去磁盘查询?
    2. 为什么在通过主键索引查询数据时, 符合条件的数据以单条数据的方式读到内存中而不是以一整个数据页的方式读到内存中?

    作者回复: 1. 通过普通索引也会,InnoDB的访问模式都是先内存,不在内存中,才到磁盘找;
    2. 是以数据页的方式读到内存的,然后在从内存的这个数据页(默认16k)里面找到数据。

    
     2
  • Geek_02538c
    2019-02-02
    过年了,还有新文章,给个赞。 另,where 和 order 与索引的关系,都讲过了,group by 是否也搞个篇章说一下。

    作者回复: 你说得对^_^ 第37篇就是,新年快乐

    
     2
  • Ryoma
    2019-02-02
    read_rnd_buffer_length 参数应该是 read_rnd_buffer_size,见文档:https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_read_rnd_buffer_size

    作者回复: 你说得对,多谢

    发起勘误了

    新年快乐

    
     2
我们在线,来聊聊吧