引入索引下推:适用场景
首先我们仍以员工表为例,引入概念:索引下推- 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=1 DEFAULT CHARSET=utf8 COMMENT='员工记录表';
- INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
- INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
- INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());
- ‐‐ 插入一些示例数据
- 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('zhuge',i),i,'dev');
- set i=i+1;
- end while;
- end;
- delimiter ;
- call insert_emp();
复制代码 like KK% 一般情况都会走索引- #都走了全索引
- EXPLAIN SELECT * FROM employees WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position='manager';
- EXPLAIN SELECT * FROM employees WHERE (name = 'LiLei' or name = 'HanMeimei') AND age = 22 AND position='manager';
复制代码 </ol>所以什么是索引下推呢?
对于辅助的联合索引(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 like 'LiLei%' AND age = 22 AND position ='manager';
- EXPLAIN SELECT * FROM employees_copy WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
复制代码 对于上面这两种 name>'a' 和 name>'zzz' 的执行结果,mysql最终是否选择走索引或者一张表涉及多个索引,mysql最终如何选择索引,我们可以用trace工具来一查究竟,开启trace工具会影响mysql性能,所以只能临时分析sql使用,用完之后立即关闭
trace工具用法
- #很显然不走索引
- EXPLAIN select * from employees where name > 'a';
- #通过覆盖索引使其走name部分索引
- EXPLAIN select name,age,position from employees where name > 'a' ;
- #mysql估算该条件数据较少,也走了索引
- EXPLAIN select * from employees where name > 'zzz' ;
复制代码 Order by与Group by优化
Case1:EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND position ='manager' order by age;
利用最左前缀法则:中间字段不能断,因此查询用到了name索引,从key_len=74也能看出,age索引列用在排序过程中,因为Extra字段里没有using filesort
Case 2:EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' order by position;
key_len=74,查询使用了name索引,由于用了position进行排序,跳过了age,出现了Using filesort。
Case 3:EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' order by age,position;
查找只用到索引name,age和position用于排序,无Using filesort。
Case 4:EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' order by position,age;
和Case 3中explain的执行结果一样,但是出现了Using filesort,因为索引的创建顺序为
name,age,position,但是排序的时候age和position颠倒位置了
Case 5:EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age =18 order by position,age;
与Case 4对比,在Extra中并未出现Using filesort,因为age为常量,在排序中被优化,所以索引未颠倒,不会出现Using filesort。
Case 6:EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' order by age asc,position desc;
虽然排序的字段列与索引顺序一样,且order by默认升序,这里position desc变成了降序,导致与索引的排序方式不同,从而产生Using filesort。Mysql8以上版本有降序索引可以支持该种查询方式。
Case 7:EXPLAIN SELECT * FROM employees WHERE name in ('LiLei','Lucy') order by age,position;
对于排序来说,多个相等条件也是范围查询
Case 8:- mysql> set session optimizer_trace="enabled=on",end_markers_in_json=on; ‐‐开启trace
- mysql> select * from employees where name > 'a' order by position;
- mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;
- 结论:全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描
- mysql> select * from employees where name > 'zzz' order by position;
- mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;
- 查看trace字段可知索引扫描的成本低于全表扫描,所以mysql最终选择索引扫描
- mysql> set session optimizer_trace="enabled=off"; ‐‐关闭trace
复制代码总的来说,where条件与order by排序字段的排列组合要遵循最左前缀原则
分页优化查询
很多时候我们业务系统实现分页功能可能会用如下sql实现
select * from employees limit 10000,10;
表示从表 employees 中取出从 10001 行开始的 10 行记录。看似只查询了 10 条记录,实际这条 SQL 是先读取 10010条记录,然后抛弃前 10000 条记录,然后读到后面 10 条想要的数据。因此要查询一张大表比较靠后的数据,执行效率是非常低的。
优化技巧:
连续自增主键
- # Using where; Using filesort
- EXPLAIN SELECT * FROM employees WHERE name > 'a' order by name;
- # 用覆盖索引优化:Using where; Using index
- EXPLAIN SELECT name,age,position FROM employees WHERE name > 'a' order by name;
复制代码 改写后的 SQL 走了索引,扫描行数大大减少,执行效率更高。
但是很显然这样的场景并不实用,因为表中可能某些记录被删后,主键空缺,导致结果不一致
另外如果原 SQL 是 order by 非主键的字段,按照上面说的方法改写会导致两条 SQL 的结果不一致。所以这种改写得满足以下两个条件:- #没添加单独order by,表示通过主键排序
- select * from employees limit 90000,5;
- #与查询结果一致
- select * from employees where id > 90000 limit 5;
复制代码 根据非主键字段排序的分页查询
再看一个根据非主键字段排序的分页查询,SQL 如下:为什么没走索引排序呢?扫描整个索引并查找到没索引的行(可能要遍历多个索引树)的成本比扫描全表的成本更高,所以优化器放弃使用索引。(扫描整个二级索引还要回表)
如何优化:
其实很简单啦。排序时返回尽量少的字段,或者说是索引覆盖的字段,所以可以像这样优化:- select * from employees ORDER BY name limit 90000,5;
- #Using filesort key:null -> 全表扫描
- EXPLAIN select * from employees ORDER BY name limit 90000,5;
复制代码 原 SQL 使用的是 filesort 排序,而优化后的 SQL 使用的是索引排序。
Join关联查询优化
首先我们先引入一些测试数据:
[code]‐‐ 示例表:CREATE TABLE `t1` (`id` int(11) NOT NULL AUTO_INCREMENT,`a` int(11) DEFAULT NULL,`b` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_a` (`a`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;create table t2 like t1;‐‐ 插入一些示例数据‐‐ 往t1表插入1万行记录drop procedure if exists insert_t1;delimiter ;;create procedure insert_t1()begin declare i int; set i=1; while(icount(字段)>count(主键 id) //字段有索引,count(字段)统计走二级索引,二级索引存储数据比主键索引少,所以count(字段)>count(主键 id)</p>字段无索引:count(*)≈count(1)>count(主键 id)>count(字段) //字段没有索引count(字段)统计走不了索引,count(主键 id)还可以走主键索引,所以count(主键 id)>count(字段)
count(1)跟count(字段)执行过程类似,不过count(1)不需要取出字段统计,就用常量1做统计,count(字段)还需要取出字段,所以理论上count(1)比count(字段)会快一点。
count(*) 是例外,mysql并不会把全部字段取出来,而是专门做了优化,不取值,按行累加,效率很高,所以不需要用count(列名)或count(常量)来替代 count(*)。
为什么对于count(id),mysql最终选择辅助索引而不是主键聚集索引?因为二级索引相对主键索引存储数据更少,检索性能应该更高,mysql内部做了点优化(应该是在5.7版本才优化)。
索引设计原则
- 代码先行,索引后上
- 联合索引尽量覆盖条件
- 不要在小基数字段上建立索引
- 长字符串我们可以采用前缀索引
- where与order by冲突时优先where
- 基于慢sql查询做优化 ,可以根据监控后台的一些慢sql,针对这些慢sql查询做特定的索引优化
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除 |