Mysql索引优化demo

索引优化案例分析

  • 首先看这篇博文你对执行计划各个字段有所了解,如果没有请先点击explain执行计划.

单表分析

  • 首先创建一个表并添加一些数据:

    创建表和一些假数据:
    create table if not exists `article`(
    `id` int(10) unsigned not null primary key auto_increment,
    `author_id` int(10) unsigned not null,
    `category_id` int(10) unsigned not null,
    `views` int(10) unsigned not null,
    `comments` int(10) unsigned not null,
    `title` varbinary(255) not null,
    `content` text not null);
    
    insert into `article`(`author_id`,`category_id`,`views`,`comments`,`title`,`content`) values
    (1,1,1,1,'1','1'),
    (2,2,2,2,'2','2'),
    (1,1,3,3,'3','3');
    
  • 查询category_id =1并且comments大于1,被看过最多那条记录的id,看看没有加索引的情况下的执行计划:

    explain select id,author_id from article where category_id = 1 and comments > 1 order by views desc limit 1;
    

    type=ALL表示全表扫描,并且Extra有Using filesort,存在filesort表示查询效果很坏。
    

    因我们查询使用category_id,comments,views三个字段,尝试给它们3个字段添加索引:

    create index idx_article_ccv on article(`category_id`,`comments`,`views`);
    

    此时再查看执行计划:

    type=range,使用到了索引,但是Extra 还是有filesort,这是不行的。但是我们已经建立索引了,为什么会没有用到。这是因为BTree索引工作原理,先排序category_id.如果遇到相同的category_id则再排序comments,如果遇到comments再排序views,当comments字段联合索引处于中间位置时,因为comments>1条件为范围值,这样MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效。

    drop index idx_article_ccv on article;
    

    更换索引给category_id,views创建索引

    create index idx_article_cv on article(category_id,views);
    

    再查看执行计划:

    可以看到type=ref,索引类型更好了。冰鞋Extra没有filesort,category_id为覆盖索引第一个它功能用于查找,第二个索引views在order by 后面用于排序。不会像上例中第二个索引用于排序后造成第三个索引失效,从而导致sql内部使用filesort进行排序。

双表分析

  • 创建表 和 数据

    create table if not exists `class`(
    `id` int(10) unsigned not null auto_increment,
    `card` int(10) unsigned not null,
    primary key(`id`)
    );
    
    create table if not exists `book`(
    `bookid` int(10) unsigned not null auto_increment,
    `card` int(10) unsigned not null,
    primary key(`bookid`)
    );
    # 添加一些数据:
    insert into class(card) values(floor(1+(rand()*20)));
    ...
    insert into book(card) values(floor(1+(rand()*20)));
    ...
    

    在没有建立索引状态下查询执行计划:

    explain select * from class left join book on book.card = class.card;
    

    显然这样很糟糕,2个查询type都为ALL,这样导致全表扫描。

    当然创建索引并不是一下子就创建成功的,我们需要不断的调换寻求最佳方法,所以首先给右表book的card添加索引:

    alter table `book` add index Y (`card`);
    

    查看执行计划:

    左连接(left join)将索引加在右表中,type=ref非唯一性索引扫描,并且rows也由原来20+21变成20+1
    

    那么我们尝试给左表class的card添加索引效果会如何呢?

    # 删除之前创建索引
    drop index Y on book;
    # 给左表class的card添加索引
    alter table `class` add index Y (`card`);
    

    查看执行计划:

    可以type=index,当然不如上面创建索引ref好,并且rows为20+21,也不如上面创建的索引好。
    这么看来左连接把索引加在右表上会比较好
    

    那么右连接(left join)会怎样呢?道理是相同的,这里不进行演示

  • 结论:

    左连接,索引创建右表,右连接索引建在左表上。
    # 当然你也可以通过调换2个表位置,也是可以的
    

三张表分析

  • 原有基础上再增加一张表,再添加一些数据

    create table if not exists `phone`(
    `phoneid` int(10) unsigned not null auto_increment,
    `card` int(10) unsigned not null,
    primary key(`phoneid`)
    )engine=innodb;
    
    # 添加点假数据
    insert into phone(card) values(floor(1+(rand()*20)));
    ...
    

    再没有添加索引情况下,先看看执行计划:

    select * from class left join book on class.card = book.card left join phone on book.card = phone.card;
    

    显然type=ALL全表扫描,key都为NULL没有用到索引。

    因查询方向是class-->book--->phone,连接方式为left join,那么从双表分析得到一些诀窍,给book和phone添加索引

    alter table `phone` add index z (`card`);
    alter table `book` add index Y (`card`);
    

    查看执行计划:

    执行计划后两行的type都成为ref,有人会问为什么第一行type还是ALL,当然第一个执行语句需要全表扫描来驱动整个sql语句。而且看rows时候20+1+1 当然好过 20+21+20.读取记录的行数也少了不少。
    

小总结

  • Join语句优化:

    - 尽可能减少join语句的NestedLoop循环总次数,永远用小的结果集驱动大的结果集.
    - 优先优化NestedLoop的内层循环 (鸡蛋黄,鸡蛋清,鸡蛋壳道理)
    - 保证Join语句中被驱动表上Join条件字段已经被索引。
    - 当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝啬JoinBuffer的设置。
    
原文地址:https://www.cnblogs.com/xujunkai/p/12492460.html