CREATE TABLE 'employees'(
'id' int(11) NOT NULL AUTO INCREMENT,
'name' varchar(24) NOT NULL DEFAULT '' COMMENT'姓名',
'age' int(11) NOT NULL DEFAULT 'O' COMMENT '年龄',
'position' varchar(28) 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=4 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('xzh',i),i,'dev');
set i=i+1;
end while;
end;;
delimiter ;
call insert_emp()
一、常见情况
1.遵循最左侧原则使用Index索引
利用最左侧原则,name执行索引查询,但是age断掉了,key_len = 74;还是使用index排序;
2.最左侧原则使用Index索引和文件排序
这里name还是执行了索引查询,但是position跳过了age又用了position排序,所以从age层需要使用文件排序,Extra会出现Using fileSort
3. 最左侧原则使用全部索引
这里虽然只用到了name索引,但是age和position用于排序了,遵循索引(排好序的数据结构)原则,还是会走索引。但是key_len =74,不是140
4.最左侧原则右侧索引失效
这是name执行了索引,但是age和position顺序不对,导致后面的索引失效,还是会使用文件排序 using filesort
5.最左侧原则排序顺序错误
这里order by 后面虽然age排再position后面,但是age为常量,在排序中被优化,所以索引未颠倒,使用了nage和age排序,key_len=78,并且不会出现文件排序using fileSort
6.降序排序导致索引失效
b+tree默认是升序排序,这里position使用了desc,导致顺序不一致,索引失效;从而使用using filesor.MySql8以上的版本又降序索引可以支持这种查询。
所以说项目实战中尽量使用升序排序查询。
7.In查询索引失效
对于排序来说,多个相等条件也是范围查询
8.优化范围查询走索引
上一章中我们讲到,索引第一个字段使用范围查询,sql优化器会认为全表扫秒更快。使用排序后会加载所有数据使用using fileSort。
如何优化呢?
使用覆盖索引
二、优化总结
1.MySql支持index和filesort两种排序。
Using index 是MySql扫秒索引本身完成排序。index 高效,filesort拿到内存或磁盘中排序,效率低;
2.Order by 满足两种情况会使用using index:
1) order by语句使用最左侧原则;
2)where子句与order by 子句条件列组合满足最左侧原则;
3.尽量在索引上完成排序(建立时的最左侧原则);
4.如果order by子句的条件上不在或者不尊循最左侧原则就会产生using filesort
5.能使用覆盖索引尽量使用覆盖索引
6.group by 和 order by 性质一致
原则上都时先排序后分组,需要遵循索引创建时的索引顺序。对于group by的优化,不需要排序的可以设置order by null;
where优先级高于having,能使用where覆盖的,不要使用haveing.
三、Using filesort文件排序原理详
filesort文件排序方式单路排序:
单路排序:一次性取出满足条件行的所有字段,然后在sort buffer中进行排序;
用trace工具可以看到sort_mode信息里显示< sort_key, additional_fields >或者< sort_key, packed_additional_fields >
双路排序(又叫回表排序模式):是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行ID,然后在 sort buffer 中进行排序,排序完后需要
再次取回其它需要的字段;
用trace工具可以看到sort_mode信息里显示< sort_key, rowid > 。
MySQL 通过比较系统变量 max_length_for_sort_data(默认1024字节) 的大小和需要查询的字段总大小来判断使用哪种排序模式。
如果字段的总长度小于max_length_for_sort_data ,那么使用单路排序模式;如果字段的总长度大于max_length_for_sort_data ,那么使用双路排序模式。
对比两个排序模式:
单路排序会把所有需要查询的字段都放到 sort buffer 中,而双路排序只会把主键和需要排序的字段放到sort buffer 中进行排序,然后再通过主键回
到原表查询需要的字段。如果 MySQL 排序内存 sort_buffer 配置的比较小并且没有条件继续增加了,可以适当把max_length_for_sort_data 配置小点,让优化
器选择使用双路排序算法,可以在sort_buffer 中一次排序更多的行,只是需要再根据主键回到原表取数据。如果 MySQL 排序内存有条件可以配置比较大,可以
适当增大 max_length_for_sort_data 的值,让优化器优先选择全字段排序(单路排序),把需要的字段放到 sort_buffer 中,这样排序后就会直接从内存里返回查
询结果了。所以,MySQL通过 max_length_for_sort_data 这个参数来控制排序,在不同场景使用不同的排序模式,从而提升排序效率。
注意:如果全部使用sort_buffer内存排序一般情况下效率会高于磁盘文件排序,但不能因为这个就随便增大sort_buffer(默认1M),mysql很多参数设置都是做过
优化的,不要轻易调整。
四、索引设计原则
1.代码先行,索引后上
一般应该等到主体业务功能开发完毕,把涉及到该表相关sql都要拿出来分析之后再建立索引。
2.联合索引尽量覆盖条件
比如可以设计一个或者两三个联合索引(尽量少建单值索引),让每一个联合索引都尽量去包含sql语句里的where、order by、group by的字段,还要确保这些联合索引的字段顺序尽量满足sql查询的最左前缀原则
3.尽量在大基数字段上建立索引
索引基数是指这个字段在表里总共有多少个不同的值,比如一张表总共100万行记录,其中有个性别字段,其值不是男就是女,那么该字段的基数就是2。如果对这种小基数字段建立索引的话,还不如全表扫描了,因为你的索引树里就包含男和女两种值,根
本没法进行快速的二分查找,那用索引就没有太大的意义了.一般建立索引,尽量使用那些基数比较大的字段,就是值比较多的字段,那么才能发挥出B+树快速二分查找的优势来。
4.长字符串采用前缀索引
尽量对字段类型较小的列设计索引,比如说什么tinyint之类的,因为字段类型较小的话,占用磁盘空间也会比较小,此时你在搜索的时候性能也会比较好一点。当然,这个所谓的字段类型小一点的列,也不是绝对的,很多时候你就是要针对varchar(255)这
种字段建立索引,哪怕多占用一些磁盘空间也是有必要的。对于这种varchar(255)的大字段可能会比较占用磁盘空间,可以稍微优化下,比如针对这个字段的前20个字符建立索引,就是说,对这个字段里的每个值的前20个字符放在索引树里,类似
于 KEY index(name(20),age,position)。此时你在where条件里搜索的时候,如果是根据name字段来搜索,那么此时就会先到索引树里根据name字段的前20个字符去搜索,定位到之后前20个字符的前缀匹配的部分数据之后,再回到聚簇索引提取出来完整的
name字段值进行比对。但是假如你要是order by name,那么此时你的name因为在索引树里仅仅包含了前20个字符,所以这个排序是没法用上索引的, group by也是同理。所以这里大家要对前缀索引有一个了解
5.where 与order by 冲突时优先使用where
在where和order by出现索引设计冲突时,到底是针对where去设计索引,还是针对order by设计索引?
到底是让where去用上索引,还是让order by用上索引?
一般这种时候往往都是让where条件去使用索引来快速筛选出来一部分指定的数据,接着再进行排序。因为大多数情况基于索引进行where筛选往往可以最快速度筛选出你要的少部分数据,然后做排序的成本可能会小很多。
6.基于慢Sql查询做优化
可以根据监控后台的一些慢sql,针对这些慢sql查询做特定的索引优化。
五、索引设计实战
这里以社交场景APP来举例:
我们一般会去搜索一些好友,这里面就涉及到对用户信息的筛选,这里肯定就是对用户user表搜索了,这个表一般来说数据量会比较大,我们先不考虑分库分表的情况:
比如,我们一般会筛选地区(省市),性别,年龄,身高,爱好之类的,有的APP可能用户还有评分,比如用户的受欢迎程度评分,我们可能还会根据评分来排序等等。
对于后台程序来说除了过滤用户的各种条件,还需要分页之类的处理,可能会生成类似sql语句执行:select xx from user where xx=xx and xx=xx order by xx limit xx,xx
对于这种情况如何合理设计索引了,
比如用户可能经常会根据省市优先筛选同城的用户,还有根据性别去筛选,那我们是否应该设计一个联合索引 (province,city,sex) 了?
这些字段好像基数都不大,其实是应该的,因为这些字段查询太频繁了。假设又有用户根据年龄范围去筛选了,比如 where province=xx and city=xx and age>=xx and age
注意,一般这种范围查找的条件都要放在最后,之前讲过联合索引范围之后条件的是不能用索引的,但是对于当前这种情况依然用不到age这个索引字段,因为用户没有筛选sex字段,那怎么优化了?
其实我们可以这么来优化下sql的写法:where province=xx and city=xx and sex in ('female','male') and age>=xx and age
假设可能还有一个筛选条件,比如要筛选最近一周登录过的用户,一般大家肯定希望跟活跃用户交友了,这样能尽快收到反馈,对应后台sql可能是这样:where province=xx and city=xx and sex in ('female','male') and age>=xx and age= xx那我们是否能把 latest_login_time 字段也加入索引了?比如 (province,city,sex,hobby,age,latest_login_time) ,显然是不行的,那怎么来优化这种情况了?其实我们可以试着再设计一个字段is_login_in_latest_7_days,用户如果一周内有登录值就为1,否则为0,那么我们就可以把索引设计成 (province,city,sex,hobby,is_login_in_latest_7_days,age) 来满足上面那种场景了!
一般来说,通过这么一个多字段的索引是能够过滤掉绝大部分数据的,就保留小部分数据下来基于磁盘文件进行order by语句的排序,最后基于limit进行分页,那么一般性能还是比较高的。不过有时可能用户会这么来查询,就查下受欢迎度较高的女性,比如sql:where sex = 'female' order by score limit xx,xx,那么上面那个索引是很难用上的,不能把太多的字段以及太多的值都用 in 语句拼接到sql里的,那怎么办了?
其实我们可以再设计一个辅助的联合索引,比如 (sex,score),这样就能满足查询要求了。以上就是给大家讲的一些索引设计的思路了,核心思想就是,尽量利用一两个复杂的多字段联合索引,抗下你80%以上的查询,然后用一两个辅助索引尽量抗下剩余的一些非典型查询,保证这种大数据量表的查询尽可能多的都能充分利用索引,这样就能保证你的查询速度和性能了!