MySQL自带查询优化器(MySQL Query Optimizer)
- MySQL中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算机分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(系统认为最优的数据检索方式,不见得是DBA认为是最优的,这部分最耗费时间)
- 当客户端向MySQL请求一条Query,命令解析器模块完成请求分类,区别处是SELECT并转发给MySQL Query Optimizer时,MySQL Query Optimizer 会先对整条Query进行优化,处理吊一些常量表达式的预算,直接换算成常量值,并对Query中的查询条件进行简化和转换,去掉一些无用或显而易见的条件、结构调整等。然后分析Query中的hint信息(如果有),看现实Hint信息是否可以完全确定该Query的执行计划。如果没有Hint或Hint信息不足以完全确定执行计划,则会读取索设计对象的统计信息,根据Query进行写相应的计算分析,然后在得出最后的执行计划。
MySQL常见瓶颈
- CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据的时候
- IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
- 服务器硬盘的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态
如果SQL优化器没有更改,并且这些瓶颈也没有出现,那么调出MySQL分析报告来看看到底MySQL哪里惹了事。来学习一下查询解析计划Explain
Explain
是什么?(查看执行计划)
- 使用Explain关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的,分析你的查询语句或是表结构的性能瓶颈。
能干吗?
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
怎么玩?
- Explain + SQL语句
- 执行计划包含的信息
![76af4eb8c156b66b4ad0cd259678c243.png](https://i-blog.csdnimg.cn/blog_migrate/5abeaa6f4ec32f36d10dab24297bb433.png)
各字段解释
id
- select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
- 三种情况
- id相同,执行顺序由上至下
![0bd1c66da632e8285b9a04dadd50c4d3.png](https://i-blog.csdnimg.cn/blog_migrate/e057d0a288dcb84886514e8ed98c165e.jpeg)
id相同,执行顺序从上到下,搭配table列进行观察可知,执行顺序为t1->t3->t2。
2. id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
![fdff9703781db6aa43855e28088e8b8d.png](https://i-blog.csdnimg.cn/blog_migrate/b01fc87365e93f395cf4f968cfae8c3b.jpeg)
如果是子查询id的序号会递增,id值越大执行优先级越高,搭配table列可知,执行顺序为t3->t1->t2。
3. id相同不同,同时存在
![17cfe156a5845fc81701f46b261bc1f1.png](https://i-blog.csdnimg.cn/blog_migrate/12b724d39af96e8b8f5a439611046e90.jpeg)
id如果相同,可认为是同一组,执行顺序从上到下。在所有组中,id值越大执行优先级越高。所以执行顺序为t3->derived2(衍生表,也可以说临时表)->t2。
总结:id的值表示select子句或表的执行顺序,id相同,执行顺序从上到下,id不同,值越大的执行优先级越高。
select_type
- SIMPLE : 简单的select查询, 查询中不包含子查询或者UNION
- PRIMARY : 查询中包含任何复杂的子查询, 最外层查询被标记为PRIMARY
- SUBQUERY : 在SELECT或WHERE列表中包含子查询
- DERIVED : 在FROM列表中包含的子查询被标记为DERIVED(衍生), MySQL会递归执行这些子查询, 把结果放在临时表里
- UNION : 若第二个SELECT出现在UNION之后, 则被标记为UNION; 若UNION包含在FROM子句的子查询中, 外层SELECT被标记为 : DERIVED
- UNION RESULT : 从UNION表获取结果的SELECT
table
- 显示这一行的数据是关于哪张表的
type
- 访问类型, 显示查询使用了何种类型, 从最好到最差依次是 :
system>const>eq_ref>ref>range>index>ALL
一般来说, 要保证查询至少达到range级别, 最好能达到ref
- system :表只有一行记录(等于系统表), 这是const类型的特例, 平时不会出现
- const :表示通过索引一次就找到了, const用于比较primary key或者unique索引。因为只匹配一行数据, 索引很快, 如将主键置于where列表中, MySQL就能将该查询转换为一个“常量”
![3a5e74c8f4643c160f047d7e5e4339cc.png](https://i-blog.csdnimg.cn/blog_migrate/731463edf701cfb81e828872ef758801.png)
- eq_ref :唯一性索引扫描, 对于每个索引键, 表中只有一条记录与之匹配。常见于主键或唯一索引扫描
![45276779edaace096bdde4f56f3f70ca.png](https://i-blog.csdnimg.cn/blog_migrate/658667113640df8254ed19607178cebb.png)
- ref :非唯一性索引扫描, 返回匹配某个单独值的所有行。本质上也是一种索引访问, 它返回所有匹配某个单独值的行, 可能会找到多个符合条件的行, 所以这个应该属于查找和扫描的混合体
![d0edd49a91974b5bd5b6c66046f8d9fd.png](https://i-blog.csdnimg.cn/blog_migrate/471e2afaca2160cd86701416c1107105.jpeg)
eq_ref和ref:就好比一个班级里面,只有一个班主任和一群学生,t2返回的只有一个记录(就就好比班主任),而col1返回的是所有col1等于ac(所有名字是ac的学生)
- range :只检索给定范围的行, 使用一个索引来选择行。key列显示使用了哪个索引, 一般就是在where语句中出现了between, < ,> ,in等的查询。这种范围索引扫描比全表扫描要好, 因为它只需要开始于索引的某一点, 而结束于另一点, 不用扫描全部索引。
![453b21ffd3b1417f85cc535959ea7c53.png](https://i-blog.csdnimg.cn/blog_migrate/004406e1b3842085fd35e5194d1fc583.jpeg)
- index :Full Index Scan, index与ALL的区别为index类型只遍历索引树。这通常比ALL快, 因为索引文件通常比数据文件小。(也就是说虽然ALL和Index都是读全表, 但index是从索引中读取的, 而all是从硬盘中读取的)
![3b83188c3ed63ed38ebb8ccf34548665.png](https://i-blog.csdnimg.cn/blog_migrate/5f60824207bfcdc648f2b745bf82820b.png)
- ALL :Full Table Scan(全表扫描), 将遍历全表以找到匹配的行
![23d56e81bebe6131e39704ecf6c71758.png](https://i-blog.csdnimg.cn/blog_migrate/b016e1ac1c0f81034ba00f31aa21b030.png)
possible_keys
- 显示可能应用在这张表中的索引, 一个或多个。
- 查询涉及到的字段是若存在索引, 则该索引将被列出, 但不一定被查询实际使用
简而简之:possible_keys是MySQL分析出推测可能用到的索引有哪几个,而key最后实际用到的索引。(理论:聚会中大概能来多少人和实际来多少人的区别)
key
- 实际使用的索引, 如果为NULL, 则没有使用索引。(要么没建索引,要么建了索引没用,所谓索引失效)
- 查询中若使用了覆盖索引, 则该索引仅出现在key列表中
覆盖索引演示 :
![fdc1ed2629e1afb26a65bebb408ee465.png](https://i-blog.csdnimg.cn/blog_migrate/79de76c3decfebdee3c6d42c4eb7f1bd.jpeg)
- possible_keys,key演示 :
![8dc92bc6957317eddee182d086e1db79.png](https://i-blog.csdnimg.cn/blog_migrate/67b7423ea04f933a1ae6fc70fc8fb747.jpeg)
举例 :理论上t1这张表应该用到PRIMARY,idx_t1这两个索引,但是实际中却只用到了idx_t1这一个月索引。
key_len
- 表示索引中使用的字节数, 可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下, 长度越短越好。(投入少,产物丰富,话句话说就是:既不给马儿吃草,又让马儿跑)
- key_len显示的值为索引字段的最大可能长度, 并非实际使用长度, 即key_len是根据表定义计算而得, 不是通过表内检索获得的(同样的查询结果,key_len用的越少越好)
举例:假设班级的表中在名字列加上一个索引,我们要根据名字去查询名字姓李的,此时我们还想去根据城市去查询,此时的查询条件比单查名字时更精确。此时得到的结果更精确,但是却投入了更多的条件(第一次名字,第二个城市),此时的key_len会比上一次更多。
下图所示:
![ea971370fe60834b1c064372bc678d44.png](https://i-blog.csdnimg.cn/blog_migrate/c2e45390dfcffa6319a0bfd86313c589.jpeg)
ref
显示索引的哪一列被使用了, 如果可能的话, 最好是一个常数。哪些列或常量被用于查找索引列上的值
![5d0815feb267c1233e3dfd31278be612.png](https://i-blog.csdnimg.cn/blog_migrate/e63b99f677231cd771af1cde00e45823.jpeg)
MySQL处理这条语句顺序加载(t2)后加载t1,t1中ref为shared.t2.col1和const,前面代表shared库中t2表的col1字段在和t2表中的col1做关联,const代表t1表中的col2匹配了ac这个常量。
rows
根据表统计信息及索引选用情况, 大致估算出找到所需记录所需要读取的行数(被张表有多行被优化器优化过)
![2c11723825a8b3ed59cca87893b5c54c.png](https://i-blog.csdnimg.cn/blog_migrate/22f8c3ba0f5b7a02b57e6e0bc1cd2171.png)
没有索引的时候,两张表关联后的加载顺序t2>t1,type就是上面介绍eq_ref和ref区别是介绍的班主任(唯一扫描,只有一行匹配),t2.col1='ac'就好比这个班级是ac,两个表加起来读取了641行。
![837457ccf2d230478a9077b48e5b9a19.png](https://i-blog.csdnimg.cn/blog_migrate/c09f7576122b8ceba8caa2f6427fd049.jpeg)
建立复合索引之后(idx_col1_col2),所需要读取的行数为143。
Extra
包含不适合在其他列中显示但十分重要的额外信息
- Using filesort : 说明MySQL会对数据使用一个外部的索引排序, 而不是按照表内索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为"文件内排序"。
![6bd4f683b6ea0d3a2c66a667cba4cf67.png](https://i-blog.csdnimg.cn/blog_migrate/4c0673adf2d108296a1a827190f9fc7a.jpeg)
Extra中包含Using where和Using index,确实看到了where条件和使用了索引(idx_col1_col2_col3),还出现了Using filesort,也就说索引只是部分使用到了。我们知道索引干两件事(排序、快速查询),查询的时候部分使用到了(key不为null并且有值,而且type是ref,ref为const),但是排序没有用到。
![cb760bf5a810223b3f65aaaed67ddd1f.png](https://i-blog.csdnimg.cn/blog_migrate/455ca03d393bdc2cf89d146a93eb8141.jpeg)
前一章索引有点说过(查询中排序的字段,排序的字段若通过索引去访问将大大提高排序速度),MySQL自我分析之后的结果和前者的区别:从两条SQL中可以看出修改后后者SQL性能高于前者,后者你怎么修的路(索引),那么我就按照你修好的路走(索引),但是前者也同样查出来了,但是修的路,并没有都走,而是自己在内部产生了一次排序,同比性能,前者在内部自己折腾了一次进行了排序结果得到的结果并没有后者好。(建立索引之后,尽可能访问的时候也是按照索引的顺序)
- Using temporary : 同比前者性能更差,使用了临时表保存中间结果, MySQL在对查询结果排序时使用了临时表。常见于排序order by和分组查询group by。
tip:临时表的创建是很伤系统性能的,因为搬数据搬到临时表,用完之后再把临时表回收,数据库内部要自己折腾,这时候查询数据几百万几千万条数据,空间要申请的多,搬数据也多,最后还需要释放,严重增加数据库负担。
![afdadd1679f94e4177428bbf091920b6.png](https://i-blog.csdnimg.cn/blog_migrate/4bb32e37ff840737de77fff90e5207b6.jpeg)
![e4cda2f8e45f587b863489edf2c5ace1.png](https://i-blog.csdnimg.cn/blog_migrate/947f46adfd5a5326080ae9563c7e629e.jpeg)
分析两者SQL,使用复合索引(idx_col1_col2,只要key不为null就是用到索引不要怀疑),这种SQL就慢的无比,如果数据是百万千万级别系统会被拖慢。避免临时表的创建。
- Using index : 表示相应的SELECT操作中使用了覆盖索引(Covering Index), 避免了访问表的数据行, 效率还可以
- 如果同时出现Using where, 表明索引被用来执行索引键值的查找;
![bd4003a74f89fd83ca81240aaec12f1d.png](https://i-blog.csdnimg.cn/blog_migrate/e990ac3b4c086269bd95809691f045d2.png)
创建的索引中包含col1和col2,并且查询的列就包含col2,部分列跟索引重合匹配。
2. 如果没有出现同时出现Using where, 表明索引用来读取数据而非执行查找动作
![d9a768cf78ce7dad0e9f39e4eb30cf67.png](https://i-blog.csdnimg.cn/blog_migrate/a21bb2b61f72028e084473e31b93ce20.png)
同上,只是SQL中并没有根据条件去查找
覆盖索引(Covering Index),一说为索引覆盖
理解方式一:就是select的数据列只用从索引中就能够获取,不比读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所见的索引覆盖。(也就是说建的索引是col1,col2,col3的复合索引,刚好查询的也是这几列或者部分满足)
理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的行,因此他不比读取整个行,毕竟索引叶子节点存储了他们所引用的数据,当能通过读取索引就可以得到想要的数据,那就不需要读取行了,一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。
注意:如果使用覆盖索引,一定要注意select列表中只去除需要的列,不可select *;因为如果将所有的字段一起做索引会导致索引文件过大,查询性能下降。
- Using where : 使用了where过滤
- Using join buffer : 使用了连接缓存
- 索引优化MIN/MAx操作或者对于MyIsam存储引擎优化COUNT(*)操作, 不必等到执行阶段再进行计算, 查询执行计划生成阶段即完成优化。Impossible WHERE : where子句值总是false, 不能用来获取任何数据, 如name=‘张三’ and name=‘李四’(不可能一个人名字是张三,又是李四吧)
- SELECT tables optimized away : 在没有group by子句的情况下, 基于
- distinct : 优化distinct操作, 在找到第一匹配的元组后即停止找同样值的动作
热身Case
![aa7c0d9e80cf7cf914e95afc10157c2f.png](https://i-blog.csdnimg.cn/blog_migrate/248c81061a3ed6f9226252ee911f8244.jpeg)
第一行(执行顺序4):id列为1,表示union里的第一个select,select_type列的primary表示查询为外层查询,table列被标记为<devied3>,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。【select d1,name...】
第二行(执行顺序2):id为3,是整个查询中第三个select的一部分,因查询包含在from中,所以为derived。【select id,name from t1 where other_column=''】
第三行(执行顺序3):select列表中的子查询select_type为subquery,为整个查询中的第二个select。【select id from t3】
第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,直接执行【select name,id from t2】
第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的<union1,4>表示用第一行和第四行的select结果进行union操作。【两个结果union操作】