本章中,我们将使用第一章中建立的100万条记录的表,显示进行从无到有的一个mysql优化的过程。
 

当前我们的表并没有使用任何索引做过优化,现在我们对表进行一次查询,来观察一下查询时间。

首先我们先查看表中是否使用索引:

show index from big_table;

MySQL单表慢查询解决 mysql单表查询性能_主键

上图可以看出,big_table表已经有了主键索引。但是没有建立其他的索引。现在在这个基础之上,我们来进行实战

1.尽量不要使用select *

select * from big_table a 【耗时:3.083s】

为什么会这么慢,通过explain来分析下

MySQL单表慢查询解决 mysql单表查询性能_主键_02


select_type:SIMPLE表示查询语句是一个单一的查询语句

filtered:100 表示返回结果的行占需要读到的行(rows列的值)的百分比

type:ALL 说明进行了全表扫描,从而表明这条查询语句性能很差。

所以证明:select *会使用全表扫描,会导致索引失效,以至于查询效率低下

 

select uid from big_table 【耗时:1.745s】

可以看到,当select后面指定字段后,查询速度明显改善了,这是为什么呢,通过explain来看一下

MySQL单表慢查询解决 mysql单表查询性能_主键_03


通过type为index,key为primary这说明这条查询语句使用了索引。

可以看出,这里不使用select * ,使用uid,便可以是原本为主键索引的uid发挥其索引的作用,这样便可以直接通过所以查出数据,而不用在去全表扫描了。

--------------------------------------------------------------------------------------------------------------------------------------------------------------

2.当select col1,col2,...时,col1,col2最好都是索引字段。
在select后面的字段,最好都是建立索引的字段,如果有又一个未建立索引则会导致全表扫描,从而无法通过索引提高性能。
看例子:

#uid为主键索引,passwd未建立索引
select uid,passwd from big_table 【耗时:2.049】

当select中的字段存在为建立索引的字段时,是否使用了全表扫描呢?通过explain来看一下:

MySQL单表慢查询解决 mysql单表查询性能_字段_04

图1

type为ALL,说明当前的sql使用了全表扫描。未能通过索引进行优化。

那么怎样才能使用上索引的优化呢?根据标题所示,我们通过将select的字段全都建立上索引,然后再来看一下查询效果。两步的sql如下:

#给字段passwd创建索引
ALTER TABLE `big_table` ADD INDEX idx_passwd ( `passwd` ) 

select uid,passwd from big_table 【1】【耗时:1.558s】

当给passwd字段也创建索引后,现在select的两个字段全都是为索引的字段了,可以看到查询的速度,比之前要快了。下面通过explain再来验证一下:

MySQL单表慢查询解决 mysql单表查询性能_主键_05

图2

从结果可以看出,图2的type为index,而图1的type为ALL,这就说明索引生效了。通过key和Extra俩个字段也能看出使用到了索引。

所以在使用select的时候,select的字段尽量都建立了索引。

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------
3.带有检索条件的查询
3.1 能用=号就用=号

select * from big_table a where uid = 102194    【1】【耗时:0.001s】
select uid from big_table a where uid = 102194  【2】【耗时:0.001s】

select passwd from big_table a  where uid=102194【3】【耗时:0.038s】
select passwd from big_table a  where passwd='f0d0ae10ab4f693101024557c8effc95'【4】【耗时:1.524s】

--注意看:同样where用一个字段作为条件,但是使用带有主键索引的uid,显然速度要快的多

结论:
uid是主键,自动会建立主键索引。当用主键索引作为检索条件时,不管是select全部字段还是单个字段,时间都会很少,所以使用主键索引作为查询条件会加快检索速度。
但是如果使用普通的字段作为检索条件如【4】,即便是select一个字段,查询速度也会很慢。所以查询条件尽量是索引,一遍提高查询速度。

下面通过用explain来分析一下

explain select * from big_table a where uid = 102194    【1】 
explain select uid from big_table a where uid = 102194  【2】

explain select passwd from big_table a  where uid = 102194 【3】
explain select passwd from big_table a  where passwd = 'f0d0ae10ab4f693101024557c8effc95' 【4】

MySQL单表慢查询解决 mysql单表查询性能_数据库_06

图1

图1中type和ref都是const,const:当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量。

MySQL单表慢查询解决 mysql单表查询性能_字段_07

图2

图2中type和ref同业阳朔const,另外Extra为Using index,说明使用了覆盖索引,查询的字段本身为索引(select uid中的uid本身为主键索引),那么就是覆盖索引。

MySQL单表慢查询解决 mysql单表查询性能_主键_08

图3

图3与图1相同,都是用索引所谓条件,select单个字段。这说明select 的这个字段是什么与查询速度无关。

MySQL单表慢查询解决 mysql单表查询性能_mysql_09

图4

图4与前三个图大不相同了,type变为了ALL,说明是全文检索,并未用到索引。Extra中Using where通常来说,意味着全表扫描或者在查找使用索引的情况下,但是还有查询条件不在索引字段当中。所以【4】的查询速度会那么慢。

所以就需要优化了。

另外你可以发现,查询速度快的rows的值都会相对小。图4的rows为994222说明查找了994222行才找到了结果。而图2的rows为1,说明一下命中。这也就解释了为什么【2】的查询速度块的原因之一了。

优化:
通过上面图4的explain可以看出,之所以慢是因为进行了全文检索,而没有用到索引,因为passwd没有索引。下面我们通过给passwd建立索引,看看是否能够优化查询速度:
首先,为passwd字段创建普通索引(关于每种索引的意思可百度)

ALTER TABLE `big_table` ADD INDEX idx_passwd ( `passwd` ) 【数据为100W条,创建索引时间为:11.427s】

#注意:创建索引是需要花费时间的,数据越多,创建索引用的时间也越长

下面我们再来执行一下【4】这条sql,比较一下现在的执行时间

select passwd from big_table a  where passwd='f0d0ae10ab4f693101024557c8effc95'【4】【耗时:0.002s】

#看以看到,当给passwd创建索引后,查询速度就特别快了

最后我们再通过explain来与图4对比一下

MySQL单表慢查询解决 mysql单表查询性能_mysql_10


可以看到type变成了ref,说明使用了索引,key为idx_passwd表示用了idx_passwd这个索引,Extra也变为了Using index也说明用到了索引。rows也变为了1说明只通过一行就找到了结果。


 

3.2尽量不要使用like,如果一定要使用,将like的%放到字符串的右边
首先对passwd字段建立索引,上面我已经建立过了,就不在建立了,语句就在上面,然后我们来看一下%在不同的位置,查询速度是怎样的:

select passwd from big_table a  where passwd like '%f0d0ae10ab4f693101024557c8effc95' 【1】【耗时:1.241s】
select passwd from big_table a  where passwd like 'f0d0ae10ab4f693101024557c8effc95%' 【2】【耗时:0.037s】
select passwd from big_table a  where passwd like '%f0d0ae10ab4f693101024557c8effc95%'【3】【耗时:1.249s】

上面的结果可以看出只有%号在字符串的右边,速度才是最快的。为什么呢,我们通过explain来看一下:

【1】和【3】执行explain的结果是相同的

MySQL单表慢查询解决 mysql单表查询性能_字段_11


【2】执行explain

MySQL单表慢查询解决 mysql单表查询性能_主键_12


注意看rows字段,【2】的rows为1,从这就看出为什么%放在字符串右边查询速度快。

所以:最好不要使用like,如果非要使用like,最好是在最右边加%。这样才能让所以发挥效果。



3.3使用between

--未使用索引字段的速度
explain  select sex from big_table a where sex BETWEEN 0 and 1 【1】【耗时:1.714s】--速度慢

MySQL单表慢查询解决 mysql单表查询性能_mysql_13


between and的字段未建立索引时,可以看出type为ALL,采用全表扫描。所以会很慢

 

--使用索引字段的速度
explain select sex from big_table a where uid BETWEEN 102194 and 102194  【2】【耗时:0.001s】

MySQL单表慢查询解决 mysql单表查询性能_数据库_14


当使用索引作为between and 的字段时,索引生效,所以查询速度很快。注意这里between and的是同一个值,如果不同值,explain的结果会怎样呢,看下面这个

explain  select sex from big_table a where uid BETWEEN 102194 and 102198 【3】【耗时:0.001s】

MySQL单表慢查询解决 mysql单表查询性能_MySQL单表慢查询解决_15


注意看,当between and的值不是同一个值时,type变为了range,不再是const了。这就引出了一个问题,比如我们这里是BETWEEN 102194 and 102198,如果这2个数字的范围差太大时,也就是rows的值越大时,那么效率就会越低下,查询速度就会越慢。

 

下面将看一下in和exists,在这之前,我们先来看一些两者一些区别,以便更好的理解:

IN 和 EXISTS 的区别

1.in()适合B表比A表数据小的情况。

2.exists()适合B表比A表数据大的情况,当A表数据与B表数据一样大时,in和exists的效率差不多,可以任选一个使用。

详细解释:

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

以上查询使用了in语句,in()只执行一次,它查出B表中的所有id字段并缓存起来.之后,检查A表的id是否与B表中的id相等,如果相等则将A表的记录加入结果集中,直到遍历完A表的所有记录.可以看出,当B表数据较大时不适合使用in(),因为B表数据全部遍历一次。

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

以上查询使用了exists语句,exists()会执行A.length次,它并不缓存exists()结果集,因为exists()结果集的内容并不重要,重要的是结果集中是否有记录,如果有则返回true,没有则返回false.

当B表比A表数据大时适合使用exists(),因为它没有那么遍历操作,只需要再执行一次查询就行.

如:A表有10000条记录,B表有1000000条记录,那么exists()会执行10000次去判断A表中的id是否与B表中的id相等.

如:A表有10000条记录,B表有100000000条记录,那么exists()还是执行10000次,因为它只执行A.length次,可见B表数据越多,越适合exists()发挥效果.

再如:A表有10000条记录,B表有100条记录,那么exists()还是执行10000次,还不如使用in()遍历10000*100次,因为in()是在内存里遍历比较,而exists()需要查询数据库,我们都知道查询数据库所消耗的性能更高,而内存比较很快.

结论:

前表大用in,前表小用exists,当A表数据与B表数据一样大时,in与exists效率差不多,可任选一个使用.

 

3.4使用in(not in)

select * from big_table a where uid in (102194)    【1】【耗时:0.047s】
select uid from big_table a where uid in (102194)  【2】【耗时:0.001s】

select passwd from big_table a where uid in (102194) 【3】【耗时:0.001s】
select sex from big_table a where sex in (0) 【4】【耗时:1.524s】--速度慢

结论:
【1】【2】【3】的查询速度都很快因为查询条件in用的uid是主键索引。【1】和【2】【3】差的的时间是因为select *导致的。
【4】条件中的in用的sex不是索引,所以查询速度会慢。
下面通过explain再来看一下

explain select * from big_table a where uid in (102194)    【1】
explain select uid from big_table a where uid in (102194)  【2】

explain select passwd from big_table a where uid in (102194) 【3】
explain select sex from big_table a where sex in (0) 【4】

MySQL单表慢查询解决 mysql单表查询性能_数据库_16

图1

MySQL单表慢查询解决 mysql单表查询性能_MySQL单表慢查询解决_17

图2

MySQL单表慢查询解决 mysql单表查询性能_MySQL单表慢查询解决_18

图3

MySQL单表慢查询解决 mysql单表查询性能_数据库_19

图4

总结:

图1,图2,图3通过type和ref的const可以看出,查询语句是可接受的,都用到主键索引优化了查询速度

图4的type为ALL,说明是通过全文搜索来查找满足条件的记录。rows为994227也说明了扫描了这么多条数据才找到了满足条件的记录。从而也证明了type为ALL(一共100万条记录,这里经过90多万的扫描,所以查询时间慢)。


3.5使用exists

select * from big_table a where exists (select * from big_table where uid = 102194)    【1】【耗时:2.494s】#速度慢 慢在第一个主查询【2.459s】
select sex from big_table a where exists (select * from big_table where uid = 102194)  【3】【耗时:1.836s】#速度慢
select sex from big_table a where exists (select * from big_table where sex = 0)       【4】【耗时:1.587s】#速度慢

结论:
上面三个查询从时间上可以发现,速度都相当慢。为什么会这么慢呢?还记得上面说in和exists区别的时候讲过,当使用exsits时,不会缓存记录,主表每条都会查询一次,这也就是为什么所有的查询都这么慢。我们来佐证一下这个问题,我们单独选择子查询时,因为uid为索引,所以瞬间就可以查出子查询的结果,但是当我们选中主查询是,会发现因为要全表查一次,所以会很慢。这也就是为什么说不建议使用exsits,如果非要用则如果主表(前表)记录少时再用exists。

下面我们再通过explain来分析一下慢的原因:

explain select * from big_table a where exists (select * from big_table where uid = 102194)    【1】
explain select sex from big_table a where exists (select * from big_table where uid = 102194)  【2】
explain select sex from big_table a where exists (select * from big_table where sex = 0)       【3】

MySQL单表慢查询解决 mysql单表查询性能_MySQL单表慢查询解决_20

图1


MySQL单表慢查询解决 mysql单表查询性能_字段_21

图2


MySQL单表慢查询解决 mysql单表查询性能_字段_22

图3


执行explain后,出现两条记录,这是因为现在的查询语句有了子查询。因为有了子查询,就会牵扯到执行顺序问题,所以这里先讲一下怎么来看查询顺序。那就是观察explain结果记录中的id字段。

id的情况有三种,分别是:

1. id相同表示加载表的顺序是从上到下。
2. id不同,id值越大,优先级越高,越先被执行。
3. id有相同,也有不同,同时存在。id相同的可以认为是一组,从上往下顺序执行;在所有的组中,id的值越大,优先级越高,越先执行

结论:
所以,从图1可以看出id为2先执行,他执行的是什么?通过table为big_table可以知道这个子查询就是exsits后面的那个查询语句。我们观察id为2这一行,通过type为const,ref为const,rows为1可以看出这个子查询速度没有问题。
然后再来看id为1这一行,table为a说明这条记录是针对sql中的主查询进行的分析,他的type为ALL,说明进行了全文检索,rows为994227说明大约遍历了994227行记录才执行完毕。通过这两个点,就可以确定这就是慢的主要原因。也是将来需要进行优化的点。
图2与图1效果是一样的。因为自语句都用了主键索引,所以子查询速度都很快。瓶颈在主查询上
图3则略有不同,我们发现id为2的这条记录type也为ALL,rows也是994227,说明也是进行了全文检索。因为自查询中的where sex不是索引。所以这也增加了检索时间。这样子查询和主查询都需要进行优化。
 

4 其他

select * from big_table a where uid>102194 【1】【耗时:2.514s】#速度慢

#在上面的select * from big_table a where uid=102194时,瞬间就可以查询出结果
#这里主要为了来观察其区别

MySQL单表慢查询解决 mysql单表查询性能_MySQL单表慢查询解决_23

图1

结论:

通过explain可以看出,当使用>,<,<=,>=,<>这些范围相关的比较运算符时,虽然where中明明用了主键索引uid为条件,但还是会使索引失效。这也就是为什么rows为497113,type为range,而不是像之前where uid=102194时rows为1,type为const了。也就是查询速度慢的一个原因了。

 

---------------------------------------------------------------------------------------------------------------------------------------------------------------------
通过这一章,我们运行不同的sql,然后配合explain来观察了在百万级的记录情况下,简单的查询语句在单条件情况下的各种执行速度和分析。

第三章,将会继续看一下单表多条件的情况下,执行速度,以及explain的分析。