当前位置: 首页 > 科技观察

超全的Explain总结,妈妈再也不用为我的SQL优化发愁了

时间:2023-03-22 14:48:14 科技观察

在select语句前加上explain关键字,MySQL会在query上设置一个flag,当query执行时,会返回执行计划信息,而不是ExecutethisSQL(如果from包含子查询,则子查询会仍然执行,结果会放在临时表中)CREATETABLE`film`(`id`int(11)NOTNULLAUTO_INCREMENT,`name`varchar(10)DEFAULTNULL,PRIMARYKEY(`id`),KEY`idx_name`(`名称`))ENGINE=InnoDBDEFAULTCHARSET=utf8;CREATETABLE`actor`(`id`int(11)NOTNULL,`name`varchar(45)DEFAULTNULL,`update_time`datetimeDEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8;创建表`film_actor`(`id`int(11)NOTNULL,`film_id`int(11)NOTNULL,`actor_id`int(11)NOTNULL,`remark`varchar(255)DEFAULTNULL,PRIMARYKEY(`id`),KEY`idx_film_actor_id`(`film_id`,`actor_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8这两个变体explainextended会在explain的基础上提供一些额外的查询优化信息。紧接着,可以通过showwarnings命令获取优化后的查询语句,以查看优化器优化了什么。另外还有一个filteredcolumn,它是一个半比值,rows*filtered/100可以估算出explain中与上表连接的行数(上表是指explain中的id值较小的比当前表id值表)mysql>explainextendedselect*fromfilmwhereid=mysql>showwarnings;explainpartitions比explain多了一个partitions字段。如果查询基于分区表,它将显示查询将访问的分区。id列id列的编号是select的序号,有几个select,就有几个id,id的顺序随着select出现的顺序递增。MySQL将select查询分为简单查询(SIMPLE)和复杂查询(PRIMARY)。复杂查询分为三类:简单子查询、派生表(from语句中的子查询)和联合查询。id列越大,执行优先级越高。如果id相同,则从上到下执行。如果id为NULL,则**select_type列select_type表示对应的行是简单查询还是复杂查询。如果是复杂查询,就是上面三个复杂查询中的哪一个。1.简单查询。查询不包含子查询和unionmysql>explainselect*fromfilmwhereid=2;2)primary:复杂查询中最外层的select3)subquery:包含在select中的子查询(不在from子句中)4)derived:包含在from子查询in子句中。MySQL会将结果存储在一个临时表中,也称为派生表(derived的英文意思)用这个例子来理解主、子查询和派生类型mysql>explainselect(select1fromactorwhereid=1)from(select*fromfilmwhereid=1)der;5)union:union中的第二个及之后的select6)unionresult:从union临时表中取出结果的select用这个例子来理解union和union的结果类型:mysql>explainselect1unionallselect1;tablecolumn这一列代表explain某行访问的是哪个表。当from子句中有子查询时,表列为format,说明当前查询依赖id=N的查询,所以先执行id=N的查询。有union时,UNIONRESULT表列的值为,1和2代表参与union的selectrowids。type列该列表示关联类型或访问类型,即MySQL如何决定在表中查找行以及数据行记录的大概范围。从最好到最差分别是:system>const>eq_ref>ref>range>index>ALL一般来说,要保证查询达到范围级别,最好是refNULL。Mysql可以在优化阶段分解查询语句,使用时不需要访问表或索引。例如:选择索引列中的最小值可以通过单独查找索引来完成,而无需访问表mysql>explainselectmin(id)fromfilm;const,systemmysql可以优化查询的某一部分并将其转换为常量(您可以看到showwarnings的结果)。当主键或唯一键的所有列都与常量进行比较时,所以表中最多只有一个匹配行,读取一次,速度比较快。system是const的特例。当表中只有一个元组匹配时,是systemmysql>explainextendedselect*from(select*fromfilmwhereid=1)tmp;eq_ref主键或唯一键索引的所有部分连接使用,最多只有一个匹配条件会被退回记录。这可能是除const之外最好的连接类型,简单的选择查询不会使用这种类型。mysql>explainselect*fromfilm_actorleftjoinfilmonfilm_actor.film_id=film.id;ref与eq_ref相比,不使用唯一索引,而是使用普通索引或唯一索引的部分前缀。索引需要与某个值进行比较,可能会找到多个匹配的条件行。1、简单select查询,name为普通索引(非唯一索引)mysql>explainselect*fromfilmwherename="film1";2、关联表查询,idx_film_actor_id是film_id和actor_id的联合索引,这里用的是film_actor的左前缀film_id。mysql>explainselectfilm_idfromfilmleftjoinfilm_actoronfilm.id=film_actor.film_id;范围扫描通常出现在in()、between、>、<、>=等操作中。使用索引检索给定范围的行。mysql>explainselect*fromactorwhereid>1;index扫描全表索引,通常比ALL快。(索引是从索引中读取的,都是从硬盘中读取的)mysql>explainselect*fromfilm;ALL是全表扫描,也就是说mysql需要从头到尾找到需要的行。通常,这需要添加索引来优化mysql>explainselect*fromactor;possible_keys列此列显示查询可能使用哪些索引来查找。解释的时候可能会出现possible_keys有columns,但是key显示NULL。这是因为表中的数据不多。mysql认为索引对本次查询帮助不大,所以选择了全表查询。如果该列为NULL,则没有关联索引。在这种情况下,可以通过检查where子句看是否可以创建合适的索引来提高查询性能,然后使用explain查看效果。keycolumn此列显示mysql实际使用哪个索引来优化对表的访问。如果没有使用索引,则此列为NULL。如果要强制mysql使用或忽略possible_keys列中的索引,在查询中使用forceindex,ignoreindex。key_len列显示了mysql在索引中使用的字节数。该值可用于计算使用了索引中的哪些列。例如film_actor的联合索引idx_film_actor_id由两个int列film_id和actor_id组成,每个int为4字节。从结果中的key_len=4可以推断查询是使用第一列:film_id列进行索引查找。mysql>explainselect*fromfilm_actorwherefilm_id=2;"key_len计算规则如下:Stringchar(n):n字节长度varchar(n):2字节存储字符串长度,如果是utf-8,则长度为3n+2值类型tinyint:1字节smallint:2bytesint:4bytesbigint:8bytes  时间类型 date:3bytestimestamp:4bytesdatetime:8bytes"如果字段允许NULL,需要1个字节记录是否为NULL。索引的最大长度为768字节。当字符串过长时,mysql会做一个类似左前缀索引的处理,提取前半部分字符进行索引。ref列显示key列记录的索引中的表查找值中使用的列或常量。常见的有:const(常量),字段名(例如:film.id)rows列是mysql估计读取检测到的行数,注意这里不是结果集中的行数。附加栏此栏显示附加信息。常见的重要值如下:Usingindex查询的列被索引覆盖,where过滤条件是索引的前导列,属于高性能表现。一般使用覆盖索引(索引包含所有查询的字段)。对于innodb,如果是辅助索引,性能会提高很多where使用索引查询的列被索引覆盖,where过滤条件是索引列之一但不是索引的前导列,表示不能直接通过索引搜索查询到符合条件的数据mysql>explainselectfilm_idfromfilm_actorwhereactor_id=1;NULLquerycolumn不被索引覆盖,where过滤条件是索引的前导列,表示索引是使用,但有些字段没有被索引覆盖,必须通过“回表”来实现。索引不是纯粹使用的,也不是完全没用的Indexmysql>explainselect*fromfilm_actorwherefilm_id=1;Usingindexcondition类似于Usingwhere,查询列不完全被索引覆盖,wherecondition是前导列范围;mysql>explainselect*fromfilm_actorwherefilm_id>1;使用temporarymysql需要创建一个临时表来处理查询。当出现这种情况时,一般需要进行优化。首先想到的是利用索引来优化。actor.name没有索引,所以创建一个临时表distinctmysql>explainselectdistinctnamefromactor;film.name创建一个idx_name索引,extra此时查询时正在使用索引,没有临时表此时mysql会根据连接类型浏览所有符合条件的记录,保存sortkey和rowpointer,然后按顺序排序key和检索row信息。这种情况一般需要考虑使用索引进行优化。actor.name不创建索引,它会浏览整个actor表,保存排序关键字name和对应的id,然后对name进行排序并检索行记录mysql>explainselect*fromactororderbyname;film.name建立了一个idx_name索引,extra是此时查询时使用indexmysql>explainselect*fromfilmorderbyname;