索引优化案例分析
- 首先看这篇博文你对执行计划各个字段有所了解,如果没有请先点击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的设置。