mysql索引优化

一、EXPLAIN

做MySQL优化,我们要善用 EXPLAIN 查看SQL执行计划。

type列,连接类型。一个好的sql语句至少要达到range级别,杜绝出现all级别。
key列,使用到的索引名。如果没有选择索引,值是NULL。可以采取强制索引方式。
key_len列,索引长度。
rows列,扫描行数。该值是个预估值。
extra列,详细说明。注意常见的不太友好的值有:Using filesort, Using temporary。

虽然 explain返回的结果项很多,这里我们只关注三种,分别是type,key,rows。其中key表明的是这次查找中所用到的索引,rows是指这次查找数据所扫描的行数。而type则是本文要详细记录的连接类型,前两项重要而且简单,无需多说。

type – 连接类型
mysql中explain的type的解释:连接类型
mysql5.7中type的类型达到了14种之多,这里只记录和理解最重要且经常遇见的六种类型,它们分别是all,index,range,ref,eq_ref,const。从左到右,它们的效率依次是增强的。撇开sql的具体应用环境以及其他因素,你应当尽量优化你的sql语句,使它的type尽量靠右,但实际运用中还是要综合考虑各个方面的。这里直接使用工作中的一张表来测试

CREATE TABLE `rcc_rule_d`  (
  `RID` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据唯一记录号',
  `RULE_ID` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则ID',
  `RULE_CODE` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则代码',
  `RULE_NAME` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则名称'
  PRIMARY KEY (`RULE_ID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Compact;

写的好的博客地址:
mysql优化中的explain的使用
explain type ref_Mysql Explain之type详解
1.all
这便是所谓的“全表扫描”,如果是展示一个数据表中的全部数据项,倒是觉得也没什么,如果是在一个查找数据项的sql中出现了all类型,那通常意味着你的sql语句处于一种最原生的状态,有很大的优化空间。
为什么这么说呢?因为all是一种非常暴力和原始的查找方法,非常的耗时而且低效。用all去查找数据就好比这样的一个情形:S学校有俩万人,我告诉你你给我找到小明,然后你怎么做呢!你当然是把全校俩万人挨个找一遍,即使你很幸运第一个人便找到了小明,但是你仍然不能停下,因为你无法确认是否有另外一个小明存在,直到你把俩万人找完为止。所以,基本所有情况,我们都要避免这样类型的查找,除非你不得不这样做。

explain SELECT * FROM `rcc_rule_d` where RULE_CODE = 'IA00000001010010000'

在这里插入图片描述
这是因为RULE_CODE列既不是主键也不是索引,因此只能采用全表扫描来查找目标RULE_CODE。

2.index
这种连接类型只是另外一种形式的全表扫描,只不过它的扫描顺序是按照索引的顺序。这种扫描根据索引然后回表取数据,和all相比,他们都是取得了全表的数据,而且index要先读索引而且要回表随机取数据,因此index不可能会比all快(取同一个表数据),但为什么官方的手册将它的效率说的比all好,唯一可能的原因在于,按照索引扫描全表的数据是有序的。这样一来,结果不同,也就没法比效率的问题了。
如果一定要比效率,只需要获取这个表的数据并且排序便可以看出来谁比谁效率高了:

explain SELECT * FROM `rcc_rule_d` order by rule_code

在这里插入图片描述

explain SELECT * FROM `rcc_rule_d` order by rule_id

在这里插入图片描述

上面可以看出,根据rule_code列排序的连接类型是all型的,但是注意extra列是用到了排序(Using filesort),
而根据rule_id列排序的连接类型是index,而且得到的结果自然是有序的,不许额外的排序。可能正是因为这个缘故,index的效率比all高,但注意这需要相同的条件才成立(既需要排序)。

如果连接类型为index,而且extra列中的值为‘Using index’,那么称这种情况为 索引覆盖;
索引覆盖意味着什么呢?想象这样一种场景,如果说一本新华字典是一张表,当然前面的索引部分(假设按照部首的索引)是这张表的索引,那么索引覆盖就相当于根据部首索引获取第一个字到最后一个字(新华字典的所有字)。我们获得了字典中所有的字,然而我们并没有查一次表,因为我们想要的都在索引中,即索引覆盖。

在这里插入图片描述

上例获取的rule_id刚好为索引列,因此无需回表取数据。

3.range
range指的是有范围的索引扫描,相对于index的全索引扫描,它有范围限制,因此要优于index。关于range比较容易理解,需要记住的是出现了range,则一定是基于索引的。同时除了显而易见的between,and以及’>‘,’<'外,in和or也是索引范围扫描。

4.ref
出现该连接类型的条件是: 查找条件列使用了索引而且不为主键和unique。其实,意思就是虽然使用了索引,但该索引列的值并不唯一,有重复。这样即使使用索引快速查找到了第一条数据,仍然不能停止,要进行目标值附近的小范围扫描。但它的好处是它并不需要扫全表,因为索引是有序的,即便有重复值,也是在一个非常小的范围内扫描。下面为了演示这种情形,给employee表中的name列添加一个普通的key(值允许重复)

alter table rcc_rule_d add key my_rule_name(`rule_name`); 
explain SELECT rule_name FROM `rcc_rule_d` where rule_name = '测试';

在这里插入图片描述

可以看到,在rcc_rule_d表中根据rule_name查找数据的时候,mysql优化器便选择了ref的连接类型。

5.ref_eq
ref_eq 与 ref相比牛的地方是,它知道这种类型的查找结果集只有一个!什么情况下结果集只有一个呢?那便是使用了主键或者唯一性索引进行查找的情况。在没有查找rule_id前我们就知道结果一定只有一个,所以当我们首次查找到这个rule_id,便立即停止了查询。这种连接类型每次都进行着精确查询,无需过多的扫描,因此查找效率更高,当然列的唯一性是需要根据实际情况决定的。

CREATE TABLE `rcc_rule_kng_cfg_a`  (
  `RID` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据唯一记录号',
  `RULE_KNG_ID` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则知识ID',
  `RULE_ID` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则ID',
  `KNG_CODE` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '知识代码',
  `KNG_NAME` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '知识名称',
  PRIMARY KEY (`RULE_KNG_ID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Compact;

explain SELECT * FROM `rcc_rule_d` a,rcc_rule_kng_cfg_a b where a.rule_id = b.rule_id

在这里插入图片描述

上面就可以看到rcc_rule_kng_cfg_a 表是全表扫描的类型,rows=3047代表外层表循环的次数(因为有能关联的数据条数就是3047行),但是rcc_rule_d表的rows怎么是1,怎么可能?刚开始也是很疑惑,这与mysql的查询原理息息相关,rows实际反映的是查询的内循环数,针对外层的每一条数据匹配,rcc_rule_d的确一次就可以命中,因此rows为1。

6.const
通常情况下,如果将一个主键放置到where后面作为条件查询,mysql优化器就能把这次查询优化转化为一个常量。至于如何转化以及何时转化,这个取决于优化器。

二、SQL语句中IN包含的值不应过多

MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。再例如:select id from table_name where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了;再或者使用连接来替换。

三、SELECT语句务必指明字段名称

SELECT *增加很多不必要的消耗(cpu、io、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发生改变时,前断也需要更新。所以要求直接在select后面接上字段名。

四、当只需要一条数据的时候,使用limit 1

这是为了使EXPLAIN中type列达到const类型

五、如果排序字段没有用到索引,就尽量少排序

六、如果限制条件中其他字段没有索引,尽量少用or

or两边的字段中,如果有一个不是索引字段,而其他条件也不是索引字段,会造成该查询不走索引的情况。很多时候使用 union all 或者是union(必要的时候)的方式来代替“or”会得到更好的效果

七、尽量用union all代替union

union和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。当然,union all的前提条件是两个结果集没有重复数据。

八、不使用ORDER BY RAND()

select id from table_name
order by rand() limit 1000;
上面的sql语句,可优化为
select id from table_name t1 join
(select rand() * (select max(id) from table_name) as nid) t2
on t1.id > t2.nid limit 1000;

九、区分in和exists, not in和not exists

select * from 表A where id in (select id from 表B)
1
上面sql语句相当于

select * from 表A where exists
(select * from 表B where 表B.id=表A.id)
1
2
区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询。所以IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。

关于not in和not exists,推荐使用not exists,不仅仅是效率问题,not in可能存在逻辑问题。如何高效的写出一个替代not exists的sql语句?

原sql语句

select colname … from A表
where a.id not in (select b.id from B表)
1
2
高效的sql语句

select colname … from A表 Left join B表 on
where a.id = b.id where b.id is null
1
2
取出的结果集如下图表示,A表不在B表中的数据

十、使用合理的分页方式以提高分页的效率

select id,name from table_name limit 866613, 20

使用上述sql语句做分页的时候,可能有人会发现,随着表数据量的增加,直接使用limit分页查询会越来越慢。

优化的方法如下:可以取前一页的最大行数的id,然后根据这个最大的id来限制下一页的起点。比如此列中,上一页最大的id是866612。sql可以采用如下的写法:

select id,name from table_name where id> 866612 limit 20

十一、分段查询

在一些用户选择页面中,可能一些用户选择的时间范围过大,造成查询缓慢。主要的原因是扫描行数过多。这个时候可以通过程序,分段进行查询,循环遍历,将结果合并处理进行展示。

如下图这个sql语句,扫描的行数成百万级以上的时候就可以使用分段查询

十二、避免在 where 子句中对字段进行 null 值判断

对于null的判断会导致引擎放弃使用索引而进行全表扫描。

十三、不建议使用%前缀模糊查询

例如LIKE “%name”或者LIKE “%name%”,这种查询会导致索引失效而进行全表扫描。但是可以使用LIKE “name%”。
那如何查询%name%?
如下图所示,虽然给secret字段添加了索引,但在explain结果果并没有使用
在这里插入图片描述
那么如何解决这个问题呢,答案:使用全文索引

在我们查询中经常会用到select id,fnum,fdst from table_name where user_name like ‘%zhangsan%’;。这样的语句,普通索引是无法满足查询需求的。庆幸的是在MySQL中,有全文索引来帮助我们。

创建全文索引的sql语法是:

ALTER TABLE table_name ADD FULLTEXT INDEX idx_user_name (user_name);

使用全文索引的sql语句是:

select id,fnum,fdst from dynamic_201606 where match(user_name) against('zhangsan' in boolean mode);

注意:在需要创建全文索引之前,请联系DBA确定能否创建。同时需要注意的是查询语句的写法与普通索引的区别

十四、避免在where子句中对字段进行表达式操作

比如

select user_id,user_project from table_name where age*2=36;

中对字段就行了算术运算,这会造成引擎放弃使用索引,建议改成

select user_id,user_project from table_name where age=36/2;

十五、避免隐式类型转换

where 子句中出现 column 字段的类型和传入的参数类型不一致的时候发生的类型转换,建议先确定where中的参数类型

十六、对于联合索引来说,要遵守最左前缀法则

举列来说索引含有字段id,name,school,可以直接用id字段,也可以id,name这样的顺序,但是name,school都无法使用这个索引。所以在创建联合索引的时候一定要注意索引字段顺序,常用的查询字段放在最前面

十七、必要时可以使用force index来强制查询走某个索引

有的时候MySQL优化器采取它认为合适的索引来检索sql语句,但是可能它所采用的索引并不是我们想要的。这时就可以采用force index来强制优化器使用我们制定的索引。

十八、注意范围查询语句

对于联合索引来说,如果存在范围查询,比如between,>,<等条件时,会造成后面的索引字段失效。

十九、关于JOIN优化

在这里插入图片描述
LEFT JOIN A表为驱动表
INNER JOIN MySQL会自动找出那个数据少的表作用驱动表
RIGHT JOIN B表为驱动表
注意:MySQL中没有full join,可以用以下方式来解决

select * from A left join B on B.name = A.namewhere B.name is nullunion allselect * from B;

尽量使用inner join,避免left join

参与联合查询的表至少为2张表,一般都存在大小之分。如果连接方式是inner join,在没有其他过滤条件的情况下MySQL会自动选择小表作为驱动表,但是left join在驱动表的选择上遵循的是左边驱动右边的原则,即left join左边的表名为驱动表。

合理利用索引

被驱动表的索引字段作为on的限制字段。

利用小表去驱动大表

二十、count(1)、count(*)与count(列名)的执行区别

count(1) and count()
当表的数据量大些时,对表作分析之后,使用count(1)还要比使用count(
)用时多了!

从执行计划来看,count(1)和count()的效果是一样的。 但是在表做过分析之后,count(1)会比count()的用时少些(1w以内数据量),不过差不了多少。

如果count(1)是聚索引,id,那肯定是count(1)快。但是差的很小的。

因为count(),自动会优化指定到那一个字段。所以没必要去count(1),用count(),sql会帮你完成优化的 因此: count(1)和count(*)基本没有差别!

1
2
3
4
5
6
7
8
count(1) and count(字段)
两者的主要区别是

(1) count(1) 会统计表中的所有的记录数, 包含字段为null 的记录。

(2) count(字段) 会统计该字段在表中出现的次数,忽略字段为null 的情况。即 不统计字段为null 的记录。

count(*) 和 count(1)和count(列名)区别

执行效果上 :

count(*)包括了所有的列,相当于行数,在统计结果的时候, 不会忽略列值为NULL
count(1)包括了忽略所有列,用1代表代码行,在统计结果的时候, 不会忽略列值为NULL
count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数, 即某个字段值为NULL时,不统计。

执行效率上:
列名为主键,count(列名)会比count(1)快
列名不为主键,count(1)会比count(列名)快
如果表多个列并且没有主键,则 count(1) 的执行效率优于 count(
如果有主键,则 select count(主键)的执行效率是最优的
如果表只有一个字段,则 select count(
)最优。

不会命中索引的情况

1.如果where后面有or,这样不会命中索引如:

select * from student where userName=‘小明’ or age=19;

如果想要or后面的也走索引的话,应该是创建两个单列索引

2.like是以%开头的不会命中索引如:

select * from student where userName like %明

3.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引如:

select * from student where userName=‘小明’ and age=19 and phone=1887821 ;

phone 定义的数据格式是字符串,但是sql中使用的整型

4.没有查询条件,或者查询条件没有建立索引

5.查询条件中,在索引列上使用函数(+/-*/)

select * from student where userName=‘小明’ and age-1=19 错误

select * from student where userName=‘小明’ and age=20 正确

采用 not in, not exist,!=, <> , is null , is not null 不会命中索引

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值