javaee论坛

普通会员

225648

帖子

335

回复

349

积分

楼主
发表于 2019-11-03 06:47:34 | 查看: 501 | 回复: 0

什么是索引?顾名思义,查找某物的标签,相当于书签,相当于目录。如果我们想在一本书中快速找到特定的主题,最快的方法时查找索引,看主题在那个页码。而对于mysql而言,如果需要查找某一行的值,可以先通过索引找对应的值,然后根据索引匹配的记录找到对应的数据行。然而,有时会发现,即使查询条件有索引,查询效率依然很慢。。。

当然,大多数索引对于查询速度的提升还是非常可观的,我今天要说的是有索引却不走索引的几种情况。

函数操作

很多时候我们在查询数据的时候,会“拜托”函数帮忙。我们往往关注查询结果而忽略了效率。(建表)

usemu;droptableifexistst1;/*如果表t1存在则删除表t1*/CREATETABLE`t1`(/*创建表t1*/`id`int(11)NOTNULLAUTO_INCREMENT,`a`varchar(20)DEFAULTNULL,`b`int(20)DEFAULTNULL,`c`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(`id`),KEY`idx_a`(`a`)USINGBTREE,KEY`idx_b`(`b`)USINGBTREE,KEY`idx_c`(`c`)USINGBTREE)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;dropprocedureifexistsinsert_t1;/*如果存在存储过程insert_t1,则删除*/delimiter;;createprocedureinsert_t1()/*创建存储过程insert_t1*/begindeclareiint;/*声明变量i*/seti=1;/*设置i的初始值为1*/while(i<=10000)do/*对满足i<=10000的值进行while循环*/insertintot1(a,b)values(i,i);/*写入表t1中a、b两个字段,值都为i当前的值*/seti=i+1;/*将i加1*/endwhile;end;;delimiter;callinsert_t1();/*运行存储过程insert_t1*/updatet1setc='2019-05-2200:00:00';/*更新表t1的c字段,值都为'2019-05-2200:00:00'*/updatet1setc='2019-05-2100:00:00'whereid=10000;/*将id为10000的行的c字段改为与其它行都不一样的数据,以便后面实验使用*/

对于上面建好的表,执行如下操作(查询表t1单独某一天的所有数据):

select*fromt1wheredate(c)='2019-05-21';

我们可以用explain来分析某条SQL命令(语句)的效率:

explainselect*fromt1wheredate(c)='2019-05-21';

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

至于用explain分析后所得表,我们重点关注type字段、key字段、select_type字段即可。type值(解释):system:查询对象只有一行数据,且只能用于MyISAM和Memory引擎的表。const:基于主键或唯一索引查询,最多返回一条结果。eq_ref:表连接时基于主键或非NULL的唯一索引完成扫描。ref:值查询,或者表间等值连接。fulltext:全文检索。ref_or_null:表连接类型是ref,但全文扫描的索引列中可能包含NULL值。index_merge:利用多个索引。unique_subquery:子查询中使用唯一索引。index_subquery:子查询中使用普通索引。range:利用索引进行范围查询。index:全索引扫描。ALL:全表扫描。(上面这些情况,查询性能从上到下依次变差!)

先回到今天的主题:上图type为ALL可以得知:并没有走索引,而是全表扫描。原因:对条件字段做函数操作不走索引。

why:我们知道,SQL索引使用的是B+树,这里不再多说,但是分析过此树结构后,我发现,索引树中存储的是列的实际值和主键值。如果拿‘2019-05-21’来匹配,将无法定位到索引树中的值,故而放弃索引,改走全表扫描。

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

select*fromt1wherec>='2019-05-2100:00:00'andc<='2019-05-2123:59:59';

我们用explain分析一下:

explainselect*fromt1wherec>='2019-05-2100:00:00'andc<='2019-05-2123:59:59';

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

经验分享:类似求某一天或者某一个月数据的需求,建议写成类似上例的范围查询,可让查询能走索引。避免对条件索引字段做函数处理。

隐式转换

什么时候发生隐式转换?当操作符与不同类型的操作对象一起使用时,就会发生类型转换以使操作兼容。某些转换是隐式的!网上找到了一份MySQL文档中文版,可以看看。

隐式转换估计是很多MySQL使用者踩过的坑,比如联系方式字段。由于有时电话号码带加、减等特殊字符,有时需要以0开头,因此一般设计表时会使用varchar类型存储,并且会经常做为条件来查询数据,所以会添加索引。而有时遇到需要按照手机号码条件(比如11111111111)去查询数据时,因为查询者看到条件是一串数字,而忽视表中对应手机号字段是varchar类型,因此写出了如下不合理的SQL:

selectuser_name,tele_phonefromuser_infowheretele_phone=11111111111;

实际情况下这条查询语句的效率是很低的。(怎么优化?别急,先往下看:)

我们来通过实验验证一下隐式转换是否能走索引:实验过程分为:先创建测试表并写入数据;测试隐式转换的查询并查看执行计划;测试正常查询,再查看执行计划。比如我们要查询a字段等于1000的值,SQL如下:

mysql>select*fromt1wherea=1000;+------+------+------+---------------------+|id|a|b|c|+------+------+------+---------------------+|1000|1000|1000|2019-05-2200:00:00|+------+------+------+---------------------+1rowinset(0.00sec)

explain结果:通过type这列可以看到是最差的情况ALL(全表扫描),通过key这列可以看到没走a字段的索引,通过rows这列可以看到进行了全表扫描。

不走索引的原因:a字段类型是varchar(20),而语句中a字段条件值没加单引号,导致MySQL内部会先把a转换成int型,再去做判断,相当于实际执行的SQL语句如下:

select*fromt1wherecast(aassignedint)=1000;

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

而当我们添加了单引号:通过type这列,可以看到是ref(基于普通索引的等值查询,比ALL性能好很多,可复习第2节<表3-type各项值解释>),通过key这列,可以看到已经走了a字段的索引,通过rows这列可以看到通过索引查询后就扫描了一行。

因此刚才遗留问题的那个例子中的sql1可以这样优化:

selectuser_name,tele_phonefromuser_infowheretele_phone='11111111111';

经验分享:在写SQL时,先看数据类型。

模糊查询

案例就不说了(所谓模糊,就是使用了通配符一类)经过试验,发现通配符不走索引。

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

select*fromt1wherealike'1111%';+------+------+------+---------------------+|id|a|b|c|+------+------+------+---------------------+|1111|1111|1111|2019-05-2200:00:00|+------+------+------+---------------------+1rowinset(0.00sec)

注意:这个优化方式必须结合业务,如果只是这样改SQL,可能会导致查询的结果不正确。

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

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

范围查询

有时你会在工作中因为要查询某个范围的数据而使用范围查询,但不知道有没有遇到过这种场景?明明范围查询的条件字段有索引,但是却全表扫描了。我们拿测试表举例,比如要取出b字段1到2000范围数据,SQL如下:

select*fromt1whereb>=1andb<=2000;

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

explainselect*fromt1whereb>=1andb<=2000;

发现并不能走b字段的索引。

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

优化建议:降低单次查询范围,分多次查询:

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

计算操作

有时我们有对条件字段做计算操作的需求,在使用SQL查询时,就应该小心了。如下例:

explainselect*fromt1whereb-1=1000;

发现,对索引字段做运算将使用不了索引。如何优化?将计算操作放在等号后面:

explainselect*fromt1whereb=1000+1;

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

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

总结

以上列出了几种条件字段有索引,但是使用不了索引的场景。因此在写SQL时应该注意这些点:1、应该避免隐式转换2、like查询不能以%开头3、范围查询时,包含的数据比例不能太大4、不建议对条件字段做运算及函数操作


您需要登录后才可以回帖 登录 | 立即注册

触屏版| 电脑版

技术支持 历史网 V2.0 © 2016-2017