• 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的设置。
      
  • 相关阅读:
    Hibernate整合C3P0实现连接池<转>
    Navicat连接数据一直是“正在连接...” 状态
    GTID复制报错处理:Last_Error: Error 'Can't drop database 'test'; database doesn't exist' on query
    pt-table-checksum解读
    Error 'Cannot add or update a child row: a foreign key constraint fails故障解决
    MySQL中explain的type类型
    MySQL中innodb表主键设计原则
    Java报错--Unsupported major.minor version 52.0
    mysql插入大量数据
    MySQL初始化故障-----mysql_config_editor中的坑
  • 原文地址:https://www.cnblogs.com/xujunkai/p/12492460.html
Copyright © 2020-2023  润新知