美文网首页
【教3妹学mysql】联合索引问题优化

【教3妹学mysql】联合索引问题优化

作者: 程序员小2 | 来源:发表于2022-09-03 16:06 被阅读0次

    插: 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
    坚持不懈,越努力越幸运,大家一起学习鸭~~~

    3妹

    3妹:2哥,你有没有看到新闻,上海地铁6号线有女子穿内衣在地铁内拍照。
    2哥:什么?还有这事,我还经常坐6号线呢,我怎么就没遇到呢。
    3妹:你得了吧,这种不雅的低俗行为,应该坚决抵制。
    2哥:是的,我也就是开个玩笑,3妹别激动。这种行为明显扰乱了公共秩序。
    3妹:嗯!!! 话说2哥你在干嘛呢。
    2哥:在学习Mysql, 联合索引问题优化。
    3妹:我也要一起学习下~ 教我教我

    讲课

    联合索引其实有两个作用:

    1.充分利用where条件,缩小范围

    例如我们需要查询以下语句:

    SELECT * FROM test WHERE a = 1 AND b = 2
    

    如果对字段a建立单列索引,对b建立单列索引,那么在查询时,只能选择走索引a,查询所有a=1的主键id,然后进行回表,在回表的过程中,在聚集索引中读取每一行数据,然后过滤出b = 2结果集,或者走索引b,也是这样的过程。 如果对a,b建立了联合索引(a,b),那么在查询时,直接在联合索引中先查到a=1的节点,然后根据b=2继续往下查,查出符合条件的结果集,进行回表。

    2.避免回表(此时也叫覆盖索引)

    这种情况就是假如我们只查询某几个常用字段,例如查询a和b如下:

    SELECT a,b FROM test WHERE a = 1 AND b = 2
    

    对字段a建立单列索引,对b建立单列索引就需要像上面所说的,查到符合条件的主键id集合后需要去聚集索引下回表查询,但是如果我们要查询的字段本身在联合索引中就都包含了,那么就不用回表了。

    3.减少需要回表的数据的行数

    这种情况就是假如我们需要查询a>1并且b=2的数据

    SELECT * FROM test WHERE a > 1 AND b = 2
    

    如果建立的是单列索引a,那么在查询时会在单列索引a中把a>1的主键id全部查找出来然后进行回表。 如果建立的是联合索引(a,b),基于最左前缀匹配原则,因为a的查询条件是一个范围查找(=或者in之外的查询条件都是范围查找),这样虽然在联合索引中查询时只能命中索引a的部分,b的部分命中不了,只能根据a>1进行查询,但是由于联合索引中每个叶子节点包含b的信息,在查询出所有a>1的主键id时,也会对b=2进行筛选,这样需要回表的主键id就只有a>1并且b=2这部分了,所以回表的数据量会变小。

    我们业务中碰到的就是第3种情况,我们的业务SQL本来更加复杂,还会join其他表,但是由于优化的瓶颈在于建立联合索引,所以进行了一些简化,下面是简化后的SQL:

    SELECT
      a.id as article_id ,
      a.title as title ,
      a.author_id as author_id 
    from
      article a
    where
      a.create_time between '2020-03-29 03:00:00.003'
    and '2020-04-29 03:00:00.003'
    and a.status = 1
    

    我们的需求其实就是从article表中查询出最近一个月,status为1的文章,我们本来就是针对create_time建了单列索引,结果在慢查询日志中发现了这条语句,查询时间需要0.91s左右,所以开始尝试着进行优化。

    为了便于测试,我们在表中分别对create_time建立了单列索引create_time,对(create_time,status)建立联合索引idx_createTime_status。

    强制使用idx_createTime进行查询

    SELECT
      a.id as article_id ,
      a.title as title ,
      a.author_id as author_id 
    from
      article a  FORCE INDEX(idx_createTime)
    where
      a.create_time between '2020-03-22 03:00:00.003'
    and '2020-04-22 03:00:00.003'
    and a.status = 1
    

    强制使用idx_createTime_status进行查询(即使不强制也是会选择这个索引)

    SELECT
      a.id as article_id ,
      a.title as title ,
      a.author_id as author_id 
    from
      article a  FORCE INDEX(idx_createTime_status)
    where
      a.create_time between '2020-03-22 03:00:00.003'
    and '2020-04-22 03:00:00.003'
    and a.status = 1
    

    优化结果:

    优化前使用idx_createTime单列索引,查询时间为0.91s

    优化前使用idx_createTime_status联合索引,查询时间为0.21s

    EXPLAIN的结果如下:

    id type key key_len rows filtered Extra
    1 range idx_createTime 4 311608 25.00 Using index condition; Using where
    2 range idx_createTime_status 6 310812 100.00 Using index condition

    原理分析

    先介绍一下EXPLAIN中Extra列的各种取值的含义

    Using filesort

    当Query 中包含 ORDER BY 操作,而且无法利用索引完成排序操作的时候,MySQL Query Optimizer 不得不选择相应的排序算法来实现。数据较少时从内存排序,否则从磁盘排序。Explain不会显示的告诉客户端用哪种排序。

    Using index

    仅使用索引树中的信息从表中检索列信息,而不需要进行附加搜索来读取实际行(使用二级覆盖索引即可获取数据)。 当查询仅使用作为单个索引的一部分的列时,可以使用此策略。

    Using temporary

    要解决查询,MySQL需要创建一个临时表来保存结果。 如果查询包含不同列的GROUP BY和ORDER BY子句,则通常会发生这种情况。官方解释:”为了解决查询,MySQL需要创建一个临时表来容纳结果。典型情况如查询包含可以按不同情况列出列的GROUP BY和ORDER BY子句时。很明显就是通过where条件一次性检索出来的结果集太大了,内存放不下了,只能通过加临时表来辅助处理。

    Using where

    表示当where过滤条件中的字段无索引时,MySQL Sever层接收到存储引擎(例如innodb)的结果集后,根据where条件中的条件进行过滤。

    Using index condition

    Using index condition 会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行;

    我们的实际案例中,其实就是走单个索引idx_createTime时,只能从索引中查出 满足a.create_time between '2020-03-22 03:00:00.003' and '2020-04-22 03:00:00.003'条件的主键id,然后进行回表,因为idx_createTime索引中没有status的信息,只能回表后查出所有的主键id对应的行。然后innodb将结果集返回给MySQL Sever,MySQL Sever根据status字段进行过滤,筛选出status为1的字段,所以第一个查询的Explain结果中的Extra才会显示Using where。

    filtered字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,这个是预估值,因为status取值是null,1,2,3,4,所以这里给的25%。

    所以第二个查询与第一个查询的区别主要在于一开始去idx_createTime_status查到的结果集就是满足status是1的id,所以去聚集索引下进行回表查询时,扫描的行数会少很多(大概是2.7万行与15万行的区别),之后innodb返回给MySQL Server的数据就是满足条件status是1的结果集(2.7万行),不用再进行筛选了,所以第二个查询才会快这么多,时间是优化前的23%。(两种查询方式的EXPLAIN预估扫描行数都是30万行左右是因为idx_createTime_status只命中了createTime,因为createTime不是查单个值,查的是范围)

    //查询结果行数是15万行左右
    SELECT count(*) from article a 
    where a.post_time 
    between '2020-03-22 03:00:00.003' and '2020-04-22 03:00:00.003'
    
    //查询结果行数是2万6行左右
    SELECT count(*) from article a 
    where a.post_time 
    between '2020-03-22 03:00:00.003' and '2020-04-22 03:00:00.003' 
    and a.audit_status = 1
    

    发散思考:如果将联合索引(createTime,status)改成(status,createTime)会怎么样?

    where
      a.create_time between '2020-03-22 03:00:00.003'
    and '2020-04-22 03:00:00.003'
    and a.status = 1
    

    根据最左匹配的原则,因为我们的where查询条件是这样,如果是(createTime,status)那么索引就只能用到createTime,如果是(status,createTime),因为status是查询单个值,所以status,createTime都可以命中,在(status,createTime)索引中扫描行数会减少,但是由于(createTime,status)这个索引本身值包含createTime,status,id三个字段的信息,数据量比较小,而一个数据页是16k,可以存储1000个以上的索引数据节点,而且是查询到createTime后,进行的顺序IO,所以读取比较快,总得的查询时间两者基本是一致。下面是测试结果:

    首先创建了(status,createTime)名叫idx_status_createTime,

    SELECT
      a.id as article_id ,
      a.title as title ,
      a.author_id as author_id 
    from
      article a  FORCE INDEX(idx_status_createTime)
    where
      a.create_time between '2020-03-22 03:00:00.003'
    and '2020-04-22 03:00:00.003'
    and a.status = 1
    

    查询时间是0.21,跟第二种方式(createTime,status)索引的查询时间基本一致。

    Explain结果对比:

    id type key key_len rows filtered Extra
    2 range idx_createTime_status 6 310812 100.00 Using index condition
    3 range idx_status_createTime 6 52542 100.00 Using index condition

    扫描行数确实会少一些,因为在idx_status_createTime的索引中,一开始根据status = 1排除掉了status取值为其他值的情况。

    索引下推push down

    查询条件为where a = '123' AND b like '%aa% AND c like '%cc%'时,按照联合索引的最左匹配法则,只有a可以用上索引,以前版本的MySQL中,innodb存储引擎就会在联合索引把满足a = '123'的数据的主键id找出来,然后回表,然后把所有数据发送给Sever端,Server端根据b like '%aa% AND c like '%cc%'对数据进行过滤,
    现在有这个push down优化,因为联合索引中有b和c两个字段的信息,innodb在联合索引查找时就会考虑到b like '%aa% AND c like '%cc%'条件,所有回表的数据就都会是满足这三个条件的,然后返回给Server端的也都是满足条件的数据行,Server端就不会再自己进行数据过滤了。

    相关文章

      网友评论

          本文标题:【教3妹学mysql】联合索引问题优化

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