一. 表结构以及数据说明:
表结构:
CREATE TABLE `employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
`age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
`position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
PRIMARY KEY (`id`),
KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=100004 DEFAULT CHARSET=utf8 COMMENT='员工记录表';
向表中插入10w条数据,可以利用存储过程/也可以利用代码,建议利用存储过程。
原因如下:
- 简单,不需要复杂的逻辑
- 不用频繁调用JDBC,浪费时间
存储过程如下:
create procedure insert_emp()
begin
declare i int;
set i=1;
while(i<=100000)DO
insert into employees(name,age,position,hire_time) values(CONCAT('ceshi',i),i,'dev',now());
set i=i+1;
end while;
end
二. 举例实际分析:
1. 联合索引第一个字段用范围不会走索引
1.1 举例说明:
explain select * from employees where name > 'ceshi' and age = 22 and position ='beijing';
possible_keys 有可能会使用到的索引,key一定会使用到的索引。
结论:从上图中可以看出,possible_keys列显示出了idx_name_age_position,意思是可能会用到这个联合索引,但是在看key这一列,是null,说明实际没有用到idx_name_age_position这个联合索引。
联合索引第一个字段就用范围查找不会走索引,mysql内部可能觉得第一个字段就用范围,结果集应该很大,回表效率不高,不如就全表扫描
2. 强制走索引
针对1.1的例子,可以强制走索引,下面以强制走索引和全表扫描做个对比:
2.1 举例说明:
explain select * from employees force index(idx_name_age_position) where name > 'ceshi' and age = 22 and position ='beijing';
根据这张截图可以看出使用到了索引,在看下不使用索引和使用索引的查询时间
1)不使用索引
2)使用索引
结论:虽然使用了强制走索引让联合索引第一个字段范围查找也走索引,扫描的行rows看上去也少了点,但是最终查找效率不一定比全表扫描高,因为回表效率不高。
3. 覆盖索引优化
3.1 什么是覆盖索引:
先说明下什么是覆盖索引,覆盖索引不是像唯一索引与联合索引那样实际的索引,而是一种概念,在所需要查询的字段中,我们的索引正好符合所有需求,这一行为就叫做覆盖索引。
比如我们要查询 年龄 姓名,我们的联合索引也有 年龄 姓名,这就是覆盖索引,使用覆盖索引的效率很高的,所以在很大程度上推荐使用覆盖索引:
3.2 举例说明:
select name,age from employees where name > 'ceshi'
结果如下:
在看下explain分析:使用到了覆盖索引
结论:使用覆盖索引在查询过程中会提高查询效率。
4. in和or
in和or在表数据量比较大的情况会走索引,在表记录不多的情况下会选择全表扫描。当然也不是绝对,大多数情况下
4.2 举例说明 in:
数据量10w:
explain select * from employees where name in ('LiLei','HanMeimei','Lucy') and age = 22 and position ='manager';
结果如下:
可以看出使用到了联合索引 idx_name_age_position
数据量10:
explain select * from employees_copy where name in ('LiLei','HanMeimei','Lucy')
and age = 22 and position ='beijjing';
结果如下:
可以看出全表扫描,没有走索引
4.3 举例说明 or:
数据量10w:
explain select * from employees where (name = 'LiLei' or name = 'HanMeimei') and age = 22 and position ='beijjing';
结果如下:
可以看出使用到了联合索引 idx_name_age_position
数据量10:
explain select * from employees_copy where (name = 'LiLei' or name = 'HanMeimei' or name = 'Lucy')
and age = 22 and position ='beijjing';
结果如下:
可以看出全表扫描,没有走索引
5. like
like KK% 一般情况都会走索引
5.2 举例说明 like:
explain select * from employees where name like 'LiLei%' and age = 22 and position ='manager';
结果如下:
可以看出使用到了索引
在看一下使用>查找的sql:
explain select * from employees where name > 'LiLei' and age = 22 and position ='manager';
根据上图可以看到联合索引只有name生效了,>号后面的索引都没有生效,看到这里,大家一定有疑问,为什么使用like后面可以的索引可以生效而>不可以呢?下面分析一下这两条sql。
首先我们要先想到B+树的结构是依次向右递增的,脑海里应该构思出一棵结构树,下面给大家画出来。
可以看出,只有当第一个索引name完全相等时,第二个字段age才是有序的,当name不想等时候,age不一定是有序的,所以不符合b+树 依次向右递增,position也是一样的道理。
那大家会问了,like也是范围查找,age和position也不一定是有序的,为什么索引会生效呢?这就需要给大家引出另外一个知识点了,索引下推。
5.3 什么是索引下推呢(Index Condition Pushdown,ICP)?
对于辅助的联合索引(name,age,position),正常情况按照最左前缀原则。
SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager'
这种情况只会走name字段索引,因为根据name字段过滤完,得到的索引行里的age和 position是无序的,无法很好的利用索引。
在MySQL5.6之前的版本,这个查询只能在联合索引里匹配到名字是 ‘LiLei’ 开头的索引,然后拿这些索引对应的主键逐个回表,到主键索 引上找出相应的记录,再比对age和position这两个字段的值是否符合。
MySQL 5.6引入了索引下推优化,可以在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可 以有效的减少回表次数。使用了索引下推优化后,上面那个查询在联合索引里匹配到名字是 ‘LiLei’ 开头的索引之后,同时还会在索引里过 滤age和position这两个字段,拿着过滤完剩下的索引对应的主键id再回表查整行数据。
like KK%其实就是用到了索引下推优化。
三. 常见sql深入优化
3.1 Order by与Group by优化
3.1.1 事例1
explain select * from employees where name = 'LiLei' and position ='manager' order by age ;
结果:
分析: 利用最左前缀法则:中间字段不能断,因此查询用到了name索引,从key_len=74也能看出,age索引列用在排序过程中,因为Extra字段里没有using filesort
3.1.2 事例2
explain select * from employees where name = 'LiLei' order by position ;
结果:
分析: 从explain的执行结果来看:key_len=74,查询使用了name索引,由于用了position进行排序,跳过了 age,出现了Using filesort。
3.1.3 事例3
explain select * from employees where name = 'LiLei' order by age,position ;
结果:
分析: 查找只用到索引name,age和position用于排序,无Using filesort。
3.1.4 事例4
explain select * from employees where name = 'LiLei' order by position,age ;
结果:
分析: 和事例 3中explain的执行结果一样,但是出现了Using filesort,因为索引的创建顺序为 name,age,position,但是排序的时候age和position颠倒位置了。
3.1.5 事例5
explain select * from employees where name = 'LiLei' and age = 22 order by position,age ;
结果:
分析: 与事例4对比,在Extra中并未出现Using filesort,因为age为常量,在查询过程中查出来的数据已经是按照age排好序的,在排序中被优化,所以索引未颠倒, 不会出现Using filesort。
3.1.6 事例6
explain select * from employees where name = 'LiLei' order by age,position desc ;
结果:
分析: 虽然排序的字段列与索引顺序一样,且order by默认升序,这里position desc变成了降序,导致与索引的 排序方式不同,从而产生Using filesort。Mysql8以上版本有降序索引可以支持该种查询方式。
Mysql索引的规则是,有序依次向右递增
四. 优化总结:
1、MySQL支持两种方式的排序filesort和index,Using index是指MySQL扫描索引本身完成排序。index 效率高,filesort效率低。
2、order by满足两种情况会使用Using index:
- order by语句使用索引最左前列
- 使用where子句与order by子句条件列组合满足索引最左前列
3、尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最左前缀法则。
4、如果order by的条件不在索引列上,就会产生Using filesort。
5、能用覆盖索引尽量用覆盖索引
6、group by与order by很类似,其实质是先排序后分组,遵照索引创建顺序的最左前缀法则。对于group by的优化如果不需要排序的可以加上order by null禁止排序。注意,where高于having,能写在where中 的限定条件就不要去having限定了。