MySQL索引优化分析系列之四

MySQL索引优化分析系列之四

查询优化

一、单表使用索引及常见索引失效

案例(索引失效)

  • 全值匹配我最爱
系统中经常出现的sql语句如下:  
  EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30  
 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4
 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'  
索引应该如何建立 ?

CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME)

建立索引前
在这里插入图片描述
索引后
在这里插入图片描述

  • 最佳左前缀法则
如果系统经常出现的sql如下:
  EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30   AND emp.name = 'abcd'   
或者
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1   AND emp.name = 'abcd'   
 那原来的idx_age_deptid_name 还能否正常使用?

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

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

在这里插入图片描述
虽然可以正常使用,但是只有部分被使用到了。

在这里插入图片描述
完全没有使用上索引。

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

  • 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
这两条sql哪种写法更好
EXPLAIN  SELECT SQL_NO_CACHE * FROM emp WHERE   emp.name  LIKE 'abc%' 
 
EXPLAIN   SELECT SQL_NO_CACHE * FROM emp WHERE   LEFT(emp.name,3)  = 'abc'

第一种
在这里插入图片描述
第二种
在这里插入图片描述

  • 存储引擎不能使用索引中范围条件右边的列
如果系统经常出现的sql如下:
 
 EXPLAIN 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)
效果

在这里插入图片描述

  • mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
CREATE INDEX idx_name ON emp(NAME)
  
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE   emp.name <>  'abc' 

在这里插入图片描述

  • is not null 也无法使用索引,但是is null是可以使用索引的
UPDATE emp SET age =NULL WHERE id=123456;
 
  下列哪个sql语句可以用到索引
  EXPLAIN SELECT * FROM emp WHERE age IS NULL
  
  EXPLAIN SELECT * FROM emp WHERE age IS NOT NULL

在这里插入图片描述

  • like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描的操作
    在这里插入图片描述
  • 字符串不加单引号索引失效
    在这里插入图片描述
    小总结

假设index(a,b,c)

Where语句 索引是否被使用
where a = 3 Y,使用到a
where a = 3 and b = 5 Y,使用到a,b
where a = 3 and b = 5 and c = 4 Y,使用到a,b,c
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 N
where a = 3 and c = 5 使用到a, 但是c不可以,b中间断了
where a = 3 and b > 4 and c = 5 使用到a和b, c不能用在范围之后,b断了
where a is null and b is not null is null 支持索引 但是is not null 不支持,所以 a 可以使用索引,但是 b不可以使用
where a <> 3 不能使用索引
where abs(a) =3 不能使用 索引
where a = 3 and b like ‘kk%’ and c = 4 Y,使用到a,b,c
where a = 3 and b like ‘%kk’ and c = 4 Y,使用到a
where a = 3 and b like ‘%kk%’ and c = 4 Y,使用到a
where a = 3 and b like ‘k%kk%’ and c = 4 Y,使用到a,b,c

一般性建议

  • 对于单键索引,尽量选择针对当前query过滤性更好的索引
  • 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
  • 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
  • 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面
  • 书写sql语句时,尽量避免造成索引失效的情况

二、关联查询优化

案例

# 下面开始explain分析
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
#结论:type 有All
 
# 添加索引优化
ALTER TABLE `book` ADD INDEX Y ( `card`);
 
换成inner join
 
delete from class where id<5;
 
# 第2次explain
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
#可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。
#这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,
#所以右边是我们的关键点,一定需要建立索引。
 
# 删除旧索引 + 新建 + 第3次explain
DROP INDEX Y ON book;
ALTER TABLE class ADD INDEX X (card);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
 

建议
1、保证被驱动表的join字段已经被索引
2、left join 时,选择小表作为驱动表,大表作为被驱动表。
3、inner join 时,mysql会自己帮你把小结果集的表选为驱动表。
4、子查询尽量不要放在被驱动表,有可能使用不到索引。
5、能够直接多表关联的尽量直接关联,不用子查询。

三、子查询优化

尽量不要使用not in 或者 not exists

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
用left outer join on xxx is null 替代
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、排序分组优化

create index idx_age_deptid_name on emp (age,deptid,name)
 
以下  是否能使用到索引,能否去掉using filesort
 
1explain  select SQL_NO_CACHE * from emp order by age,deptid; 
 
2explain  select SQL_NO_CACHE * from emp order by age,deptid limit 10; 
 
 #无过滤 不索引
 
3explain  select * from emp where age=45 order by deptid;
 
4explain  select * from emp where age=45 order by   deptid,name; 
 
5explain  select * from emp where age=45 order by  deptid,empno;
 
6explain  select * from emp where age=45 order by  name,deptid;
 
7explain select * from emp where deptid=45 order by age;
 
#顺序错,必排序
 
8explain select * from emp where age=45 order by  deptid desc, name desc ;
9explain select * from emp where age=45 order by  deptid asc, name desc ;
 
  #方向反 必排序

ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序

索引的选择
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。

开始优化:
思路: 尽量让where的过滤条件和排序使用上索引
但是一共两个字段(deptno,empno)上有过滤条件,一个字段(ename)有索引

1、我们建一个三个字段的组合索引可否?
在这里插入图片描述
我们发现using filesort 依然存在,所以name 并没有用到索引。
原因是因为empno是一个范围过滤,所以索引后面的字段不会再使用索引了。

所以我们建一个3值索引是没有意义的
那么我们先删掉这个索引,DROP INDEX idx_age_empno_name ON emp

为了去掉filesort我们可以把索引建成
在这里插入图片描述
也就是说empno 和name这个两个字段我只能二选其一。
这样我们优化掉了 using filesort。
执行一下sql
在这里插入图片描述
在这里插入图片描述
速度果然提高了4倍。
但是
如果我们选择那个范围过滤,而放弃排序上的索引呢
建立

DROP INDEX idx_age_name ON emp
create index idx_age_eno on emp(age,empno); 

在这里插入图片描述
在这里插入图片描述
果然出现了filesort,而且type还是range光看字面其实并不美好。
我们来执行以下sql
在这里插入图片描述
在这里插入图片描述

原因是所有的排序都是在条件过滤之后才执行的,所以如果条件过滤了大部分数据的话,几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。 相对的 empno<101000 这个条件如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,所以索引放在这个字段上性价比最高,是最优选择。

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

如果不在索引列上,filesort有两种算法:
mysql就要启动双路排序和单路排序

  • 双路排序
    MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,
    读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
    从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
  • 取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
  • 单路排序
    从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,
    它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,
    因为它把每一行都保存在内存中了。
  • 结论及引申出的问题
    由于单路是后出的,总体而言好过双路
    但是用单路有问题

在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。

本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。

优化策略

  • 增大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-8192之间调整

GROUP BY关键字优化

group by 使用索引的原则几乎跟order by一致 ,唯一区别是group by 即使没有过滤条件用到索引,也可以直接使用索引。

五、最后使用索引的手段:覆盖索引

什么是覆盖索引?
简单说就是,select 到 from 之间查询的列 <=使用的索引列+主键
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用覆盖索引后
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43803285/article/details/119521114