mysql 索引优化实战

下面有一个示例的数据库,用示例来记录优化的一些点:

create table employees(
    id int not null AUTO_INCREMENT,
    name varchar(20) not null default '' comment '姓名',
    age int 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,'dev',NOW());
 insert into employees(name,age,position,hire_time) values('HanMeimei',22,'pro',NOW());
 insert into employees(name,age,position,hire_time) values('Lucy',22,'dev',NOW());
 ‐‐ 插入一些示例数据
create procedure insert_temp()
begin 
    declare i int;
-- START TRANSACTION; -- 开启事务会块一些,尝试千万数据开启数据289s  不开启事务1400才跑到250w数据
    set i=1;
    while(i<=100000)do
        insert into employees(name,age,position) values(concat('test',i),i,'dev');
        set i=i+1;
    end while;
-- COMMIT; 
end;
        
call insert_temp();    

-- 顺便记录一下坑,这段代码复制如果执行报错,自己手动敲,可能编码有问题

创建了一个员工记录表,放入了一个联合索引 idx_name_age_position  将 name , age , postion作为联合索引的字段,并且加入了三条不一样的数据以及用存储过程循环添加了十万条数据模拟演习,好了,准备工作完成,接下来我们一个个的查询来进行分析:
1. 联合索引第一个字段使用范围不会走索引
        explain select * from employees where name > 'LiLei' and age=22 and position = 'manger'

 结论:联合索引第一个字段就用范围查找不会走索引,mysql内部可能觉得第一个字段就用范围,结果集应该很大,回表效率不高,还不如就全表扫描

2. 强制走索引

        EXPLAIN SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei' AND age = 22 AND position ='manager';        

        对比一下看着好像行数少了,但是看下查询的时间

        不加索引 0.211s   加了索引 0.28s(不同机器查询时间是不一致的,和硬件什么的有关系)

结论:虽然使用了强制走索引让联合索引第一个字段范围查找也走索引,扫描的行rows看上去也少了点,但是最终查找效率不一定比全表扫描高,因为回表效率不高
 3.覆盖索引优化
        
         EXPLAIN SELECT name,age,position FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';
        查询时间 0.064s

结论: 如果有联合索引,查询时最好完全覆盖字段(实在不行,尽可能多的覆盖到),那么扫描的行数,执行时间都会减少,加快效率。

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 = 'LiLei' or name = 'HanMeimei') AND age = 22 AND position ='manager';

 再加个数据少的表:

        一样的语句

        有可能就不走索引了,道理一样可能回表,数据不多还不如全表扫描

5. like XX% 一般情况都会走索引

        EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';

索引下推

        对于辅助的联合索引(name,age,position),正常情况按照最左前缀原则,SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager' 这种情况只会走name字段索引,因为根据name字段过滤完,得到的索引行里的age和 position是无序的,无法很好的利用索引。

        MySQL 5.6引入了索引下推优化,可以在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数

        为什么范围查找Mysql没有用索引下推优化?

        估计应该是Mysql认为范围查找过滤的结果集过大,like XX% 在绝大多数情况来看,过滤后的结果集比较小,所以这里Mysql选择给 like KK% 用了索引下推优化,当然这也不是绝对的,有时like XX% 也不一定就会走索引下推(判断出来的结果集比较大吧,就不走索引了)。
6. mysql 索引选择
       
        
        如果用name索引需要遍历name字段联合索引树,然后还需要根据遍历出来的主键值去主键索引树里再去查出最终数据,成本比全表扫描还高。使用覆盖索引优化如下:

常见sql深入优化

Order by 和 Group by(mysql 8环境测试)

        情况1:

 排序按索引最左前缀不会使用文件排序,    desc 与索引排列方式不一致,导致文件排序

        情况2:        

 不满足最左前缀,索引值使用了name

        情况3:

         对于排序来说,多个相等条件也是范围查询

         情况4:

        覆盖索引优化 

分页优化

        1.自增主键分页优化

               优化前select * from employees limit 90000,5;     正常分页 0.058s 

                        数据量越大,越慢,和优化的差距越明显

               因为主键是自增并且连续的,所以可以改写成按照主键去查询从第 90001开始的五行数

               优化后: select * from employees where id > 90000 limit 5;   优化分页 0.030s

        对比执行计划,优化后明显扫描更少,type也比all优化很多

        ps: 表中可能某些记录被删后,主键空缺,导致结果不一致

     

        2. 根据非主键字段排序的分页查询

         select * from employees e inner join (select id from employees order by name limit 2000000,5) ed on e.id = ed.id;

       

   总结:

          上百万级别首先不建议做分页查那么远的数据,尝试用记录上次查询主键来做,如果带条件也需要先用条件筛选出数据,再用主键索引查询。多种方式测试最终都不能达到完全优化。具体问题具体处理吧(如分表,做书签记录等,如果id是无序的,可能就应该上搜索引擎啥的了)

        

join的优化,in和exsits优化

        尽量用小数据量的表做引擎表

        in:当B表的数据集小于A表的数据集时,in优于exists

                select * from A where id in (select id from B)

        exists:当A表的数据集小于B表的数据集时,exists优于in

                select * from A where exists (select 1 from B where B.id = A.id)

count(*)查询优化

        

        注意:以上4条sql只有根据某个字段count不会统计字段为null值的数据行

字段有索引:count(*)≈count(1)>count(字段)>count(主键 id)
        //字段有索引,count(字段)统计走二级索引,二级索引存储数据比主键索引少,所以count(字段)>count(主键 id)
字段无索引:count(*)≈count(1)>count(主键 id)>count(字段)
        //字段没有索引count(字段)统计走不了索引, count(主键 id)还可以走主键索引,所以count(主键 id)>count(字段)

         其实可以看到count(*)的效率反而最高,mysql并不会把全部字段取出来,而是专门做了优化,不取值,按行累加,效率很高,所以不需要用 count(列名)或count(常量)来替代 count(*)。

索引设计原则        

        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查询做特定的索引优化。 关于慢sql查询不清楚的可以参考这篇文章: https://blog.csdn.net/qq_40884473/article/details/89455740

总结:

        最难写的一篇,理论和实践的地方很多不一致,可能是版本导致,也可能是其他优化的地方没有考虑到,优化是要结合各种实际情况,建议用explain结合索引结构分析,网上多找找解决思路,不断的尝试。

一个想过得更好的码农---邋遢道人   

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值