Mysql学习-04 索引优化分析--查询优化

版权声明:本文为博主原创文章,转载请说明出处。 https://blog.csdn.net/weixin_43549578/article/details/84189816

索引的规则

1.索引失效情况

<1>全值匹配我最爱 

    建立索引提高效率 CREATE INDEX 索引名字 ON 表名(表字段,表字段,表字段,......)

   建立索引前:

   

   索引后:

     

<2>最佳左前缀法则
     如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。 

  EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30   AND emp.name = 'abcd'   

  

 虽然只是使用了一部分但是效率还是提高了。

 

 完全没有使用上索引

 结论:过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。

<3>不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描

这两条sql哪种写法更好
SELECT SQL_NO_CACHE * FROM emp WHERE   emp.name  LIKE 'abc%' 
 
 SELECT SQL_NO_CACHE * FROM emp WHERE   LEFT(emp.name,3)  = 'abc'

第一种:

  

第二种:

  

<4>存储引擎不能使用索引中范围条件右边的列,可以将范围索引字段放置到索引中的最右位置

 如果系统经常出现的sql如下:
 SELECT  SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = 'abc' ; 
 那么索引 idx_age_deptid_name这个索引还能正常使用么?

如果这种sql 出现较多。应该建立: create index idx_age_name_deptid on emp(age,name,deptid)
效果:

<5>mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描

<6>is not null 也无法使用索引,但是is null是可以使用索引的

<7>like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操作

<8>字符串不加单引号索引失效

总结:

2.建议

  •   对于单键索引,尽量选择针对当前query过滤性更好的索引
  •   在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
  •   在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
  •   书写sql语句时,尽量避免造成索引失效的情况
  •   尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的

单表查询优化:

        当范围条件和group by 或者 order by  的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。
 

关联查询优化:如 left join /right join/union等

建议

  • 保证被驱动表的join字段已经被索引,该索引因该给属于驱动表中的字段
  • left join 时,选择小表作为驱动表,大表作为被驱动表。
  • inner join 时,mysql会自己帮你把小结果集的表选为驱动表。
  • 子查询尽量不要放在被驱动表,有可能使用不到索引。

子查询优化:
   尽量不要使用not in  或者 not exists 用left outer join  on  xxx is null 替代

  如:

SELECT SQL_NO_CACHE * FROM emp a WHERE  id  NOT  IN(SELECT ceo FROM dept b2
 WHERE ceo IS NOT NULL) group by age having count(*)<10000

修改:

  EXPLAIN SELECT SQL_NO_CACHE a.* FROM  emp a LEFT OUTER JOIN dept b
  ON a.id =b.ceo WHERE    b.ceo IS   NULL
  group by age having count(*)<10000

order by关键字优化 

  • 尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀

      

CREATE TABLE `emp` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `name` VARCHAR(20) DEFAULT NULL,
  `age` INT(3) DEFAULT NULL,
 `deptid` INT(11) DEFAULT NULL,
  empno int  not null,
 PRIMARY KEY (`id`),
 KEY `fk_dept_id` (`deptId`)

) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
create index idx_age_deptid_name on emp (age,deptid,name) -- 创建索引
drop index idx_age_deptid_name on emp -- 删除索引
以下  是否能使用到索引,能否去掉using filesort 
1、explain  select * from emp order by age,deptid; -- Using filesort
不能 没有过滤条件 
2、 explain  select * from emp order by age,deptid limit 10; -- Using filesort
 #若无过滤排序折
3、  explain  select * from emp where age=45 order by deptid; -- Using where
4、explain  select * from emp where age=45 order by   deptid,name; -- Using where
5、explain  select * from emp where age=45 order by  deptid,empno;-- Using where; Using filesort
6、explain  select * from emp where age=45 order by  name,deptid;-- Using where; Using filesort
7、 explain select * from emp where deptid=45 order by age; -- Using where; Using filesort 
#顺序不对排序折
8、explain  select * from emp where age=45 order by age,deptid; -- Using where 
9、explain  select * from emp where age=45 order by  deptid,age; -- Using where
#值确定无须排序 
 10、 EXPLAIN  SELECT * FROM emp WHERE age=50 AND deptid >8000 ORDER BY NAME; -- Using index condition; Using where; Using filesort
11、  EXPLAIN  SELECT * FROM emp WHERE age=50 AND deptid >8000 ORDER BY deptid, NAME; -- Using index condition; Using where 
 #谁残还得谁来续
12、  explain select * from emp where age=45 order by  deptid desc, name desc ; -- Using where
13、 explain select * from emp where age=45 order by  deptid asc, name desc ;-- Using where; Using filesort
#南辕北辙排序折(排序顺序不一致)
  • ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
  • 如果不在索引列上,filesort有两种算法:mysql就要启动双路排序和单路排序

     1.双路排序

         MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。

    3.单路排序
         从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。

    结论及引申出的问题
        由于单路是后出的,总体而言好过双路, 但是用单路有问题 :在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。

  4.优化策略
             增大sort_buffer_size参数的设置
             增大max_length_for_sort_data参数的设置
             减少select 后面的查询的字段。

     
  提高Order By的速度
      1. Order by时select * 是一个大忌只Query需要的字段, 这点非常重要。在这里的影响是:
         1.1 当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
         1.2 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。
     2. 尝试提高 sort_buffer_size
        不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的  1M-8M之间调整
     3. 尝试提高 max_length_for_sort_data
         提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率.  1024-8096之间调整 

GROUP BY关键字优化

  •   group by实质是先排序后进行分组,遵照索引建的最佳左前缀
  •   当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置
  •   where高于having,能写在where限定的条件就不要去having限定了。 

 分页查询的优化---limit

 EXPLAIN    SELECT  SQL_NO_CACHE * FROM emp  ORDER  BY  deptid  LIMIT 10000,40

给deptno这个字段加上索引后:

 

没什么用。因为优化器认为limit的过滤效果并不好,所以放弃了使用索引过滤。 

优化:  先利用覆盖索引把要取的数据行的主键取到,然后再用这个主键列与数据表做关联:
EXPLAIN  SELECT  SQL_NO_CACHE * FROM emp INNER JOIN (SELECT id FROM emp e ORDER BY deptno LIMIT 10000,40) a ON a.id=emp.id
 

覆盖索引 --> 就是,select 到 from 之间查询的列 <=使用的索引列+主键

猜你喜欢

转载自blog.csdn.net/weixin_43549578/article/details/84189816