#插入一些示例数据
drop procedure if exists insert_emp;
delimiter ;;
create procedure insert_emp()
begin
declare i int;
set i=1;
while(i<=100000)do
insert into employees(name,age,position) values(CONCAT('tqq',i),i,'dev');
set i=i+1;
end while;
end;;
delimiter ;
call insert_emp();
EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';
1、联合索引第一个字段用范围不会走索引
EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';
结论:联合索引第一个字段就用返回查找不会走索引,MySQL内部可能觉得第一个字段就用范围,结果集应该很大,回表效率不高,还不如走全表扫描。
2、强制走索引
EXPLAIN SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei' AND age = 22 AND position ='manager';
结论:虽然使用了强制走索引让联合索引第一个字段范围查找也走索引,扫描的行rows看上去也少了点,但是最终查找效率不一定比全表扫描高,因为回表效率不高
‐‐ 关闭查询缓存
set global query_cache_size=0;
set global query_cache_type=0;
‐‐ 执行时间0.333s
SELECT * FROM employees WHERE name > 'LiLei';
‐‐ 执行时间0.444s
SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei';
3、覆盖索引优化
查询结果集符合最左前缀原则,减少回表。
EXPLAIN SELECT name,age,position FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';
4、in和or在表数据量比较大的时候会走索引,在表记录不多的情况下会选择全表扫描
EXPLAIN SELECT * FROM employees WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position ='manager';
做一个小实验,将employees 表复制一张employees_copy的表,里面保留两三条记录
EXPLAIN SELECT * FROM employees_copy WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees_copy WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position ='manager';
5、 like KK% 一般情况都会走索引
EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees_copy WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
索引下推(Index Condition Pushdown,ICP)
like KK%其实就是用到了索引下推优化
对于辅助的联合索引(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再回表查整行数据。
索引下推会减少回表次数,对于innodb引擎的表索引下推只能用于二级索引,innodb的主键索引(聚簇索引)树叶子节点上保存的是全行数据,所以这个时候索引下推并不会起到减少查询全行数据的效果。
为什么范围查找MySQL没有用索引下推优化?
估计应该是Mysql认为范围查找过滤的结果集过大,like KK% 在绝大多数情况来看,过滤后的结果集比较小,所以这里MySQL选择给 like KK% 用了索引下推优化,当然这也不是绝对的,有时like KK% 也不一定就会走索引下推。
MySQL如何选择合适的索引
EXPLAIN select * from employees where name > 'a';
没走索引
EXPLAIN select * from employees where name > 'zzz';
走了索引
MySQL会计算走索引和不走索引的成本,如果走索引会看走那个索引花费的成本小。
trace工具用法
set session optimizer_trace="enabled=on",end_markers_in_json=on; ‐‐开启trace
select * from employees where name > 'a' order by position;
SELECT * FROM information_schema.OPTIMIZER_TRACE;
常见sql深入优化
order by 与group by 优化
上图用了索引,如果Extra中用了Using filesort就是没有用索引
case 8:
数据量大所以走了全表扫描。
可以使用覆盖索引优化:
优化总结:
1、MySQL支持两种方式排序:index和filesort,Using index MySQL扫描索引本身完成排序,效率高,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中限定。
Using filesort文件排序原理详解
filesort文件排序方式【单路排序、双路排序(回表)】
- 单路排序
一次性取出所有的满足条件行的字段,在sort buffer中排序,trace工具可以看到sort_mode信息里显示<sort_key,additional_fields> 或则 <sort_key,packed_additional_fields> - 双路排序(回表)
首先根据相应的条件取出相应的排序字段和可以直接定位行数据的ID,然后在sort suffer中进行排序,排序完再通过ID回表取出其他需要的字段。trace工具可以看到sort_mode信息里显示<sort_key,rowid>
MySQL通过比较系统变量max_length_for_sort_data(默认1024字节)的大小和需要查询的字段总大小来判断使用那种模式:
- 如果字段的总长度小于max_length_for_sort_data,那么使用单路排序;
- 如果字段的总长度大于max_length_for_sort_data,那么使用双路排序。
explain select * from employee where name = ‘tqq’ order by position
单路排序详细过程:
1、从索引name找到第一个满足name=‘tqq’ 条件的主键id;
2、根据主键id取出整行,取出所有字段的值,存入sort Buffer中
3、从索引name找到下一个满足name = 'tqq’条件的主键id;
4、重复步骤2、3直到不满足name=‘tqq’
5、对sort buffer 中的数据按照字段position进行排序
6、返回结果给客户端
双路排序详细过程:
1、从索引name找到第一个满足name=‘tqq’ 条件的主键id;
2、根据主键id取出整行,把排序字段position和主键id两个字段放到sort buffer中
3、从索引name取出下一个满足name = 'tqq’记录的主键
4、重复步骤2、3直到不满足name=‘tqq’
5、对sort buffer中的字段position和主键id按照字段position进行排序
6、遍历排序好的id和字段position,按照id的值回到原表中取出所有字段返回给客户端
索引的设计原则
1、代码先行,索引后上
等业务主体功能开发完毕,把设计到该表相关的SQL拿出来分析之后再建立索引。
2、联合索引尽量覆盖条件
尽量少建单值索引。可以设计1-3个联合索引,让每一个联合索引都尽量去包含SQL语句里的where、order by 、group by 的字段,联合索引的字段顺序尽量满足SQL查询的最左前缀原则。
3、不要在小基数字段上建立索引
索引基数就是这个字段在表中总共有多少个不同的值。比如性别。
4、长字符串我们可以采用前缀索引
对于varchar(255)的大字段可能会比较占用磁盘空间,可以对字段的前20个字符建立索引,对这个字段里的每个值的前20个字符放在索引树中,类似key index(name(20),age,position),这样就不能使用order by 了
5、where与order by 冲突的时候优先where
一般这种时候往往都是让where条件去使用索引快速搜索出来一部分指定的数据,接着再进行排序。因为大多数情况基于索引进行where筛选往往可以最快速读筛选出你想要的数据,然后排序的成本可能会小很多
6、基于慢SQL查询做优化
可以根据监控后台的一些慢SQL,针对慢SQL查询做特定的索引优化
慢SQL查询:https://blog.csdn.net/qq_40884473/article/details/89455740
设计索引的核心思想是:尽量利用一两个复杂的多字段联合索引,抗下80%以上的查询,然后用一两个辅助索引尽量抗下剩余的一些非典型查询。保证大数据量表的查询尽可能多的都能充分利用索引,这样就能保证查询速读和性能了。