条件字段有索引,为什么查询也这么慢?

   日期:2020-05-30     浏览:120    评论:0    
核心提示:前言大家都知道索引可以提高查询速度,但是有些时候会发现,有了索引反而查询变得比之前还慢了,甚至索引没起作用,那到底是因为什么,这篇文章就带着大家了解其中的原因下面会讲解几种有索引但是查询不走索引导致查询慢的场景。函数操作@查询数据时,可能很多时候会借助一些函数实现查询。有时可能我们关注的重心在是否能查出结果,往往忽略了查询的效率。现在就一起研究对条件索引字段做函数操作,是否能用到索引?...大数据

前言

大家都知道索引可以提高查询速度,但是有些时候会发现,有了索引反而查询变得比之前还慢了,甚至索引没起作用,那到底是因为什么,这篇文章就带着大家了解其中的原因

下面会讲解几种有索引但是查询不走索引导致查询慢的场景。

函数操作

查询数据时,可能很多时候会借助一些函数实现查询。有时可能我们关注的重心在是否能查出结果,往往忽略了查询的效率。现在就一起研究对条件索引字段做函数操作,是否能用到索引?

验证对条件字段做函数操作是否能走索引
首先创建测试表,建表及数据写入语句如下:

use muke;                       

drop table if exists t1;        

CREATE TABLE `t1` (             
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a` varchar(20) DEFAULT NULL,
  `b` int(20) DEFAULT NULL,
  `c` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,  
  PRIMARY KEY (`id`),
  KEY `idx_a` (`a`) USING BTREE,
  KEY `idx_b` (`b`) USING BTREE,
  KEY `idx_c` (`c`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4;

drop procedure if exists insert_t1; 
delimiter ;;
create procedure insert_t1()        
begin
  declare i int;                    
  set i=1;                          
  while(i<=10000)do                 
    insert into t1(a,b) values(i,i);  
    set i=i+1;                        
  end while;
end;;
delimiter ;
call insert_t1();                    

update t1 set c = '2019-05-22 00:00:00';  
update t1 set c = '2019-05-21 00:00:00' where id=10000;	 

对于上面创建的测试表,比如要查询测试表 t1 单独某一天的所有数据,SQL如下:

select * from t1 where date(c) ='2020-05-21';

分析结果如下:

mysql> explain select * from t1 where date(c) ='2020-05-21';


查看图中的执行计划,type 为 ALL,key 字段结果为 NULL,因此知道该 SQL 是没走索引的全表扫描

原因:对条件字段做函数操作走不了索引。

函数操作的 SQL 优化
因此如果需要优化的话,改成 c 字段实际值相匹配的形式。因为 SQL 的目的是查询 2019-05-21 当天所有的记录,因此可以改成范围查询,如下:

select * from t1 where c>='2019-05-21 00:00:00' and c<='2019-05-21 23:59:59';

再用 explain 分析下执行计划:

mysql> explain select * from t1 where c>='2019-05-21 00:00:00' and c<='2019-05-21 23:59:59';


根据上面的结果,可确定,走了 c 字段的索引(对应关注字段 key),扫描行数 1 行(对应关注字段 rows)。

隐式转换

认识隐式转换

什么是隐式转换?
当操作符与不同类型的操作对象一起使用时,就会发生类型转换以使操作兼容。某些转换是隐式的。

隐式转换估计是很多 MySQL 使用者踩过的坑,比如联系方式字段。由于有时电话号码带加、减等特殊字符,有时需要以 0 开头,因此一般设计表时会使用 varchar 类型存储,并且会经常做为条件来查询数据,所以会添加索引。

而有时遇到需要按照手机号码条件(比如 13888888888)去查询数据时,因为查询者看到条件是一串数字,而忽视表中对应手机号字段是 varchar 类型,因此写出了如下不合理的SQL:

select user_name,tele_phone from user_info where tele_phone =13888888888; 

实际情况这条 SQL 查询效率是很低的。首先根据你的经验,思考下这条 SQL 怎么优化?

验证隐式转换是否能走索引

比如我们要查询 a 字段等于 1000 的值,SQL如下

mysql> select * from t1 where a=1000;
+------+------+------+---------------------+
| id   | a    | b    | c                   |
+------+------+------+---------------------+
| 1000 | 1000 | 1000 | 2019-05-22 00:00:00 |
+------+------+------+---------------------+
1 row in set (0.00 sec)

而这条 SQL 是否能使用索引呢?我们一起看下 explain 结果:

mysql> explain select * from t1 where a=13888888888;


通过 type 这列可以看到是最差的情况 ALL,通过 key 这列可以看到没走 a 字段的索引,通过 rows 这列可以看到进行了全表扫描。
不走索引的原因
a 字段类型是 varchar(20),而语句中 a 字段条件值没加单引号,导致 MySQL 内部会先把a转换成int型,再去做判断,相当于实际执行的 SQL 语句如下:

mysql> select * from t1 where cast(a as signed int) =13888888888;

因此又回到上面说的:对索引字段做函数操作时,优化器会放弃使用索引。

隐式转换的 SQL 优化

索引字符串列条件添加单引号,查看执行计划:

mysql> explain select * from t1 where a='13888888888';

通过 type 这列,可以看到是 ref,通过key这列,可以看到已经走了 a 字段的索引,通过rows这列可以看到通过索引查询后就扫描了一行。
因此在联系方式这个例子中的 sql 1 可以这样优化:

select user_name,tele_phone from user_info where tele_phone ='13888888888';

所以建议在写SQL时,先看字段类型,然后根据字段类型写SQL。

模糊查询

分析模糊查询
很多时候我们模糊查询数据,比如会有如下 SQL:

mysql> select * from t1 where a like '%1111%';
+------+------+------+---------------------+
| id   | a    | b    | c                   |
+------+------+------+---------------------+
| 1111 | 1111 | 1111 | 2019-05-22 00:00:00 |
+------+------+------+---------------------+
1 row in set (0.00 sec)

实际这种情况无法走索引,看下执行计划:

mysql> explain select * from t1 where a like '%1111%';


重点留意type、key、rows、Extra,发现是全表扫描。

模糊查询优化建议

修改业务,让模糊查询必须包含条件字段前面的值,然后落到数据库的查询为:

mysql> select * from t1 where a like '1111%';
+------+------+------+---------------------+
| id   | a    | b    | c                   |
+------+------+------+---------------------+
| 1111 | 1111 | 1111 | 2019-05-22 00:00:00 |
+------+------+------+---------------------+
1 row in set (0.00 sec)

这种写法是可以用到索引的,explain分析如下:

mysql> explain select * from t1 where a like '1111%';

如果条件只知道中间的值,需要模糊查询去查,那就建议使用ElasticSearch或其它搜索服务器。

范围查询

也许你会在工作中因为要查询某个范围的数据而使用范围查询,但不知道有没有遇到过这种场景?明明范围查询的条件字段有索引,但是却全表扫描了。

构造不能使用索引的范围查询
我们拿测试表举例,比如要取出b字段1到2000范围数据,SQL 如下 :

mysql> select * from t1 where b>=1 and b <=2000;

首先看下这条 SQL 的执行计划:

mysql> explain select * from t1 where b>=1 and b <=2000;

这里是引用发现并不能走b字段的索引。
原因:优化器会根据检索比例、表大小、I/O块大小等进行评估是否使用索引。比如单次查询的数据量过大,优化器将不走索引。

优化范围查询

降低单次查询范围,分多次查询:

mysql> select * from t1 where b>=1 and b <=1000;
mysql> select * from t1 where b>=1001 and b <=2000;

查看执行计划

mysql> explain select * from t1 where b>=1 and b <=1000;


因此,降低查询范围后,能正常使用索引。

实际这种范围查询而导致使用不了索引的场景经常出现,比如按照时间段抽取全量数据,每条SQL抽取一个月的;或者某张业务表历史数据的删除。遇到此类操作时,应该在执行之前对SQL做explain分析,确定能走索引,再进行操作,否则不但可能导致操作缓慢,在做更新或者删除时,甚至会导致表所有记录锁住,十分危险。

计算操作

查询条件进行计算操作的 SQL 执行效率
有时我们与有对条件字段做计算操作的需求,在使用 SQL 查询时,就应该小心了。先看下例:

mysql> explain select * from t1 where b-1 =1000;

原因:对索引字段做运算将使用不了索引。

计算操作的 SQL 优化
将计算操作放在等号后面:

mysql> explain select * from t1 where b =1000 + 1;


发现将计算操作放在等号后,能正常使用索引。

一般需要对条件字段做计算时,建议通过程序代码实现,而不是通过MySQL实现。如果在MySQL中计算的情况避免不了,那必须把计算放在等号后面。

总结

本节讲解几种条件字段有索引,但是使用不了索引的场景。因此在写 SQL 时应该注意这些点:

应该避免隐式转换
like查询不能以%开头
范围查询时,包含的数据比例不能太大
不建议对条件字段做运算及函数操作

参考资料

该文为本人学习的笔记,方便以后自己复习。参考
https://dev.mysql.com/doc/refman/5.7/en/type-conversion.html
https://www.imooc.com/read/43
取其精华整合而成。

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
更多>相关资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服