和很多同行一样,在之前被面试的生涯中,我也遇到过这个问题。只是不同的面试官问法稍有不同,最直接的就是问怎么做sql的优化。
如果是工作一两年内的话,可能多半都会如是回答
1,看表有没有建索引,没有的话建索引
2,建索引要注意作为where查询条件的字段适合建索引
3,区分性不强的字段不适合建索引
4,查询的字段或者数据太多可以减少查询字段或者数据,就是不查询多余的列与行
。。。
这些都没错,但是面试官可能更想听到的应该是关于sql执行计划的。
以mysql为例,官方文档是这么描述的
mysql的中文文档有很多,但是翻译了之后转了又转,有时候可能不是那么准确,谷歌直接翻译很多句子都不通,所以我们尝试看看原版官方文档。全当是学习技术的同时学点英文了。
简单翻译如下:
第一段:explain语句提供了一些关于mysql执行语句的信息,explain可以和select,delete,insert,replace,update语句一起使用。
第二段:explain 返回关于select语句中用到的每个表的一行信息。explain会在输出中按照mysql执行语句时读取的顺序列出用到的表。
mysql用嵌套连接方法来解析所有的连接语句。也就是说mysql从第一张表读取一行数据,然后又会在第二张表,第三张表读取
匹配的行,依此类推。当所有的表都处理完之后,mysql会输出选择到的列,并通过表列表回溯,找到匹配行数最多的表。
然后从这张表读取下一行,继续同样的方法处理下一张表。
第三段:explain的输出信息包含分区信息。此外,对于select语句而言,explain还会产生一些扩展信息,只需要在explain之后用SHOW WARNINGS即可显示。
第四段:在mysql早期发行版中,分区信息和扩展信息可以用EXPLAIN PARTITIONS 和 EXPLAIN EXTENDED来显示。
基于向后兼容性,后期的版本仍然识别这些语法,但是分区信息和扩展信息现在默认是开启的,所以PARTITIONS 和
EXTENDED关键字是多余的并且过时了。他们的使用会引起警告。在未来的mysql发行版本的语法里面很可能会被移除。
第五段:过时的PARTITIONS 和 EXTENDED关键字不能在同一个explain语句中使用。此外,这两个关键字都不能和FOMAT选项一起使用。
几点小说明:
1,增删改查语句我们都熟悉,replace可能会有点陌生。
replace具备替换拥有唯一索引或者主键索引重复数据的能力,也就是如果使用replace into插入的数据的唯一索引或者主键索引与之前的数据有重复的情况,将会删除原先的数据,然后再进行添加。
语法:replace into table( col1, col2, col3 ) values ( val1, val2, val3 )
2,mysql8.0确实已经移除PARTITIONS 和 EXTENDED语法
下面看看explain的输出列
我们执行explain语句输出的就是这12列,那么12列我们要全部搞明白吗?个人觉得没必要,搞清楚最主要的三个列就好了,其他的就大概知道是啥,面试能说出这三个其他的答不出来也没多大事,要学的要记得东西太多了,没必要花太多时间在这
一 type
这句是说:explain输出的type列描述了表之间是如何连接的。在上面那个表里面json格式的输出中,join type就是access_type属性的值。下面的列表描述了连接类型,按照最佳到最差排的序
篇幅原因,我直接翻译了,当然,会遵循官方文档的意思。感兴趣又没看过官网的可以自己去看看:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
1 system
表只有一行(=系统表),这是const连接类型的特例
2 const
表最多只有一个匹配行,在查询的开始就就读到了。因为只有一行,这一行的值会被优化器其他部分认为是常量。const是非常快的
,因为只会读一次。
当我们拿主键索引或者唯一索引跟常量值比较的时候就会用到const。下面这个查询中,tb1_name会被当做const表
SELECT * FROM tbl_name WHERE primary_key=1;
SELECT * FROM tbl_name
WHERE primary_key_part1=1 AND primary_key_part2=2;
3 eq_ref
对于前面表的所有记录而言,这个表只有一行能与之形成组合。除了system和const类型,这是最好的连接类型。当一个索引的
所有部分都被连接用到了,并且索引是PRIMARY KEY 或者 UNIQUE NOT NULL索引的时候,就会用到eq_ref
eq_ref可以用于等号操作符比较的索引列。比较的值可以是常量或者使用在此表之前读取的表中的列的表达式。下面的例子当中,
mysql用了eq_ref连接处理ref_table
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;
4 ref
对于前面表的所有记录,和索引值匹配的行都会读取。如果连接只用了索引的最左前缀或者说索引不是主键索引,也不是唯一索引
(换句话说,根据给的索引值不能选出唯一行记录),就会用到ref。如果给的索引值只会匹配到少量行,那么就算是比较好的连接
类型。
如果用=或者<=>操作符对索引列进行比较就会用到ref。下面的例子中,mysql会用ref连接处理ref_table。
SELECT * FROM ref_table WHERE key_column=expr;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;
5 fulltext
当用全文索引的时候就会用到fulltext连接
6 ref_or_null
这个连接类型和ref类似,但不一样的是mysql会进行额外的行搜索,即使这些行包括空值。这个连接类型优化多半是用在解析子查询中
。下面的例子中,mysql会用ref_or_null连接来处理ref_table。
SELECT * FROM ref_table
WHERE key_column=expr OR key_column IS NULL;
7 index_merge
这个连接类型表示用到了索引合并优化。这种情况下,输出行的索引列会包含用到的全部索引,并且key_len包含了用到的索引的最大长度。
8 unique_subquery
在一些in格式的子查询中,这种连接类型会取代eq_ref,比如
value IN (SELECT primary_key FROM single_table WHERE some_expr)
unique_subquery只是一个索引查询功能,目的是完全替换子查询,提高查询的效率。
9 index_subquery
这个连接类型和unique_subquery相似。它会取代in子查询,但只是在下面格式的子查询中非唯一索引时有效。
value IN (SELECT key_column FROM single_table WHERE some_expr)
10 range
通过索引去查找行的时候,只有给定范围的行会被检索到。输出的索引列表示哪个索引被用到了。key_len包含了用到的索引最长的部分。
这种连接时ref列是空。
用=, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, 或者 IN操作符当中任意一个,将索引列和常量进行比较时,会用到range。
SELECT * FROM tbl_name
WHERE key_column = 10;
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name
WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
11 index
index连接类型和all连接类型是一样的,只是多了一个扫描索引树。两种情况下会出现:
1,如果索引是用于查询的覆盖索引,并且能够满足所有表中需要的数据,追回扫描索引树。这种情况下,extra列会显示using index。
仅索引扫描通常比全表扫描更快,因为索引通常都比数据要小。
2,按照索引的顺序,通过索引进行全表扫描查找数据。这时在extra列不会有uses index。
当查询只用到单值索引的列时,mysq会用到这个连接类型。
12 all
对于前面的所有表记录的行组合,会进行全表扫描。如果该表是第一个未标识const的表,这样就不太好了,甚至是非常糟糕的。
通常我们可以通过增加索引来避免全表扫描,这些索引会使得mysql通过常量值或者更早扫描的表的列值检索到当前表的记录。
一般来说,要保证查询达到range级别,最好达到ref
二 key
这一列表示mysql实际采用哪个索引来优化对该表的访问。如果没有使用索引,则该列是 NULL。如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 force
index、ignore index
三 extra
关于这一列,我们先看看官网这段话
简单翻译:explain的extra列输出包含了一些关于mysql如何解析查询语句的额外信息。下面的列表逐一解释了这一列可能出现的值。
每一项也都是也都是代表一个json格式的输出,其属性为extra的值。其中一部分的属性展示比较具体。其他的以信息文本属性展示。
如果我们想要让查询更快,就要小心extra列的值为Using filesort 和Using temporary,或者,会以json格式的explain输出,因为
using_filesort 和 using_temporary_table 的值和属性是一样的。
这一列的值官网有很多,列了三四十个,有兴趣自己去看看。我们也只需要熟悉下面常见的几个就行了
1 Using index:使用覆盖索引
覆盖索引定义:mysql执行计划explain结果里的key有使用索引,如果select后面查询的字段都可以从这个索引的树中
获取,这种情况一般可以说是用到了覆盖索引,extra里一般都有using index;覆盖索引一般针对的是辅助索引,整个
查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值
官网是这样说的
简单翻译:只通过索引树信息就能从表里面检索到需要的列信息,而不需要额外的搜索实际行数据。当查询只用到单个索引的一部分时会用到这个策略。
对于存储引擎为innodb的有用户定义的聚集索引的表,只有当extra列没有出现using index时才会用到索引。连接类型为index而索引为主键,就是这种情况。
2 Using where:使用 where 语句来处理结果,并且查询的列未被索引覆盖
简单翻译:where子句用于限制哪些行与下一个表匹配或者哪些行可以发送给客户端。如果extra值不是using where 并且表的连接类型是all或者index,我们的查询语句可能会有错误,除非我们就是想要获取所有行。
Using where
在 JSON 格式的输出中没有直接对应的部分。 attached_condition
属性包含WHERE
使用的任何条件。
3 Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围
简单翻译:读表的时候通过访问索引元组并测试,再决定是否需要读取全表。这样的话,索引就等于用来推断(‘下推’)读取全表记录,除非确实有必要。
4 Using temporary:mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索引来优化。
简单翻译:为了解析查询语句,mysql需要创建一个临时表存储结果。这种情况多半发生在查询语句包含group by 和 order by子句,而子句的列以不同方式列出。
5 Using filesort:将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一 般也是要考虑使用索引来优化的。
简单翻译:mysql 必须做一个额外的过程才能按指定顺序检索行记录。根据连接类型遍历所有行进行排序并存储排序的key,从而指向匹配wehere语句的行。
然后按排好的顺序存储key并检索行记录。
6 Select tables optimized away:使用某些聚合函数(比如 max、min)来访问存在索引的某个字段
简单翻译:优化器认为 1)最多只返回一行记录,2)要生成这一行,就要读取确定的一组记录。如果要读取的行在优化阶段就能读取,就没有必要等到执行查询语句时再去读。
当查询被隐式分组(包含聚合函数但没有 GROUP BY
子句)时,第一个条件会满足。当每个使用的索引执行一次行查找时,会满足第二个条件。读取的索引数决定了要读取的行数。
虽然直接读的官方文档,很多概念还是比较抽象的,毕竟这块内容是有些难度的,只能慢慢的理解并在实践中体会。
学无止境,让学习成为一种习惯。
本人水平有限,有不对的地方请指教,谢谢。