每条SQL语句,mysql都会经过成本和规则的优化,对这个SQL选择对应的一些访问方法和顺序,包括做一些特殊的改写确保执行效率是最优的,然后优化过后,就会得到一个执行计划。
所谓的执行计划,落实到底层,无非就是先访问哪个表,用哪个索引还是全表扫描,拿到据之后如何去聚簇索引回表,是否要基于临时磁盘文件做分组聚合或者排序。
explain拿到这个SQL语句的执行计划:
explain select * from table
不同的SQL语句的执行计划长什么样子:
1、单表查询:
explain select /* from t1
select_type是SIMPLE表示查询类型是很普通的且简单。表名就是t1,all就是全表扫描,这没办法,你完全没加任何where条件,那当然只能是全表扫描了!这里直接会扫描表的聚簇索引的叶子节点,按顺序扫描过去拿到表里全部数据。rows是3457,这说明全表扫描会扫描这个表的3457条数据,说明这个表里就有3457条数据,此时你全表扫描会全部扫描出来。filtered是100%,你没有任何where过滤条件,所以直接筛选出来的数据就是表里数据的100%占比。
2、多表查询:
explain select /* from t1 join t2
这是一个典型的多表关联语句,实际上会选择一个表先查询出来数据,接着遍历每一条数据去另外一个表里查询可以关联在一起的数据,然后关联起来。
一个多表关联的执行计划。首先执行计划分为了两条,也就是会访问两个表,先看他如何访问第一个表的,针对第一个表就是t1,明显是先用ALL方式全表扫描他了,而且扫出了3457条数据。接着对第二个表的访问,也就是t2表,同样是全表扫描,因为他这种多表关联方式,基本上是笛卡尔积的效果,t1表的每条数据都会去t2表全表扫描所有4568条数据,跟t2表的每一条数据都会做一个关联,
**3、一个包含子查询的SQL语句的执行计划:*EXPLAIN SELECT / FROM t1 WHERE x1 IN (SELECT x1 FROM t2) OR x3 = 'xxxx';
该SQL 主SELECT语句的WHERE筛选条件是依赖于一个子查询的,且除此之外还有一个自己的WHERE筛选条件。
4、Union查询
EXPLAIN SELECT /* FROM t1 UNION SELECT /* FROM t2
union字句默认的作用是把两个结果集合并起来还会进行去重,所以第三条执行计划就是去重。
id:一个SELECT子句对应一个id,如果有多个SELECT就会对应多个id。但有时候一个SELECT字句涉及多个表,所以会对应多条执行计划,此时可能多条执行计划的id一样。
select_type:SIMPLE、primary和subquery的。
一般如果单表查询或者是多表连接查询,其实他们的select_type都是SIMPLE。
如果是union语句的话,就类似于select /* from t1 union select /* from t2,那么会对应两条执行计划,第一条执行计划是针对t1表的,select_type是PRIMARY,第二条执行计划是针对t2表的,select_type是UNION,这就是在出现union语句的时候,他们就不一样了。
在使用union语句的时候,会有第三条执行计划,意思是针对两个查询的结果依托一个临时表进行去重,第三条执行计划的select_type就是union_result。
如果SQL里有子查询的话,类似select /* from t1 where x1 in (select x1from t2) or x3='xxx',此时其实会有两条执行计划,第一条执行计划的select_type是PRIMARY,第二条执行计划的select_type是SUBQUERY。
5、复杂SQL查询
EXPLAIN SELECT /* FROM t1 WHERE x1 IN (SELECT x1 FROM t2 WHERE x1 = 'xxx' UNION SELECT x1 FROM t1 WHERE x1 = 'xxx');
他有一个外层查询,还有一个内层子查询,子查询里还有两个SELECT语句进行union操作
6、更复杂SQL查询
explain select /* from (select x1, count(/*) as cnt from t1 group by x1) as _t1 where cnt > 10;
FROM子句后跟了一个子查询,在子查询里是根据x1字段进行分组然后进行count聚合操作,也就是统计出来x1这个字段每个值的个数,然后在外层则是针对这个内层查询的结果集进行查询通过where条件来进行过滤。
子查询里的那个语句的执行计划,select_type是derived,意思是针对子查询执行后的结果集会物化为一个内部临时表,然后外层查询是针对这个临时的物化表执行的。
这里执行分组聚合时,是使用的index_x1这个索引来进行的,type是index,意思就是直接扫描偶了index_x1这个索引树的所有叶子节点,把x1相同值的个数都统计出来就可以了。
然后外层查询是第一个执行计划,select_type是PRIMARY,针对的table是,就是一个子查询结果集物化形成的临时表,他是直接针对这个物化临时表进行了全表扫描根据where条件进行筛选的。
type的取值
const:
select /* fromt1 where id=110
直接根据主键进行等值匹配查询,那执行计划里的type就会是const,意思就是极为快速,性能几乎是线性的。因为主键值是不会重复的,这个唯一值匹配,在一个索引树里跳转查询,基本上几次磁盘IO就可以定位到了。
EXPLAIN select /* from t1 inner join t2 on t1.id = t2.id
这里是通过两个表的id进行关联查询的。
possible_keys****的取值
possible_keys:就是在针对一个表进行查询的时候有哪些潜在可以使用的索引。
比如你有两个索引,一个是KEY(x1, x2, x3),一个是KEY(x1, x2, x4),此时要是在where条件里要根据x1和x2两个字段进行查询,那么此时明显是上述两个索引都可以使用的,那么到底要使用哪个呢?
此时就需要通过我们之前讲解的成本优化方法,去估算使用两个索引进行查询的成本,看使用哪个索引的成本更低,那么就选择用那个索引,最终选择的索引,就是执行计划里的key这个字段的值了。
key_len:就是当你在key里选择使用某个索引之后,那个索引里的最大值的长度是多少,这个就是给你一个参考,大概知道那个索引里的值最大能有多长。
ref的取值:
ref :当查询方式是索引等值匹配时,比如const、ref、eq_ref、ref_or_null这些方式时,此时执行计划的ref字段告诉你的就是:你跟索引列等值匹配的是什么?是等值匹配一个常量值?还是等值匹配另外一个字段的值?
EXPLAIN SELECT /* FROM t1 WHERE x1 = 'xxx'
针对t1表的查询,type是ref方式的,也就是说基于普通的二级索引进行等值匹配,然后possible_keys只有一个就是index_x1,针对x1字段建立的一个索引,而实际使用的索引也是index_x1,毕竟就他一个是可以用的。
key_len是589,意思就是说index_x1这个索引里的x1字段最大值的长度也就是589个字节,其实这个不算是太大,不过基本可以肯定这个x1字段是存储字符串的,因为是一个不规律的长度。
ref字段,它的意思是说,既然你是针对某个二级索引进行等值匹配的,那么跟index_x1
索引进行等值匹配的是什么?是一个常量或者是别的字段?这里的ref的值是const,意思就是说,是使用一个常量值跟index_x1索引里的值进行等值匹配的。EXPLAIN SELECT /* FROM t1 INNER JOIN t2 ON t1.id = t2.id;
此时执行计划里的ref肯定不是const,因为你跟t1表的id字段等值匹配的是另外一个表的id字段,此时 ref的值就是那个字段的名称了。
针对t1表作为驱动表执行一个全表扫描,接着针对t1表里每条数据都会去t2表根据t2表的主键执行等值匹配,所以第二个执行计划的type是eq_ref,意思就是被驱动表基于主键进行等值匹配,而且使用的索引是PRIMARY,就是使用了t2表的主键。
他只有一个x1字段建了索引,x2字段是没有索引的。
针对t1表的查询方式是range,也就是基于索引进行范围查询,用的索引是index_x1,也就是x1字段的索引,然后基于x1>'xxx'这个条件通过index_x1索引查询出来的数据大概是1987条,接着会针对这1987条数据再基于where条件里的其他条件,也就是x2='xxx'进行过滤。
这个filtered是13.00,意思是估算基于x2='xxx'条件过滤后的数据大概是13%,也就是说最终查出来的数据大概是1987 /* 13% = 258条左右。
extra的取值:
EXPLAIN SELECT x1 FROM t1 WHERE x1 = 'xxx'
首先他是访问了t1表,使用的是ref访问方法,也就是基于二级索引去查找,找的是index_x1这个索引,这个索引的最大数据长度是456字节,查找的目标是一个const代表的常量值,通过索引可以查出来25条数据,经过其他条件筛选过后,最终剩下数据是100%。
**extra的信息:*Using index是说这次查询,仅仅涉及到了一个二级索引,不需要回表,因为他仅仅是查出来了x1这个字段,直接从index_x1索引里查就行了。如果没有回表操作,仅仅在二级索引里执行,那么extra里会告诉是Using index。
SELECT / FROM t1 WHERE x1 > 'xxx' AND x1 LIKE '%xxx'
此时他会先在二级索引index_x1里查找,查找出来的结果还会额外的跟x1 LIKE '%xxx'条件做比对,如果满足条件的才会被筛选出来,这种情况下,extra显示的是Using index condition。
Using where
一般是见于你直接针对一个表扫描,没用到索引,然后where里好几个条件,就会告诉你Using where,或者是你用了索引去查找,但是除了索引之外,还需要用其他的字段进行筛选,也会告诉你Using where。
1、没用索引:
EXPLAIN SELECT /* FROM t1 WHERE x2 = 'xxx'
这里的x2是没有建立索引的,所以此时他的执行计划
针对t1表进行查询,用的是全表扫描方式,没有使用任何索引,然后全表扫描,扫出来的是4578条数据,这个时候大家注意看extra里显示了Using where,意思就是说,他对每条数据都用了WHERE x2 = 'xxx'去进行筛选。最终filtered告诉了你,过滤出来了15%的数据,大概就是说,从这个表里筛选出来了686条数据,就这个意思。
2、一个条件使用索引
如果你的where条件里有一个条件是针对索引列查询的,有一个列是普通列的筛选EXPLAIN SELECT /* FROM t1 WHERE x1 = 'xxx' AND x2 = 'xxx'
针对t1表去查询,先通过ref方式直接在index_x1索引里查找,是跟const代表的常量值去查找,然后查出来250条数据,接着再用Using where代表的方式,去使用AND x2 = 'xxx'条件进行筛选,筛选后的数据比例是18%,最终所以查出来的数据大概应该是45条。
在多表关联的时候,有的时候你的关联条件并不是索引,此时就会用一种叫做join buffer的内存技术来提升关联的性能。EXPLAIN select /* from t1 inner join t2 on t1.x2 = t2.x2
他们的连接条件x2是没有索引的,此时一起看看他的执行计划
因为要执行join,那么肯定是先得查询t1表的数据,此时是对t1表直接全表查询,查出来4578条数据,接着似乎很明确了,就是对每条数据的x2字段的值,跑到t2表里去查对应的数据,进行关联。 但是此时因为 t2表也没法根据索引来查,也是属于全表扫描,所以每次都得对t2表全表扫描一下,根据extra提示的Using where,就是根据t1表每条数据的x2字段的值去t2表查找对应的数据了,然后此时会用join buffer技术,在内存里做一些特殊优化,减少t2表的全表扫描次数。
**Using filesort:*有时我们在SQL语句里进行排序时,如果排序字段是有索引的,那么其实是直接可以从索引里按照排序顺序去查找数据的。EXPLAIN SELECT / FROM t1 ORDER BY x1 LIMIT 10
这就是典型的一个排序后再分页的语句,他的执行计划如下
这个SQL语句,他是用了index方式访问的,意思就是说直接扫描了二级索引,而且实 际使用的索引也是index_x1,本质上来说,他就是在index_x1索引里,按照顺序找你LIMIT 10要求的
10条数据罢了。 所以大家看到返回的数据是10条,也没别的过滤条件了,所以filtered是100%,也就是10条数据都返回 了。
如果我们排序的时候是没法用到索引的,此时就会基于内存或者磁盘文件来排序,大部分时候得都基于磁盘文件来排序。
EXPLAIN SELECT /* FROM t1 ORDER BY x2 LIMIT 10
x2字段是没有索引的,此时执行计划如下
他基于x2字段来排序,是没法直接根据有序的索引去找数据的,只能把所有数据写入一个临时的磁盘文件,基于排序算法在磁盘文件里按照x2字段的值完成排序,然后再按照LIMIT 10的要求取出来头10条数据。 把表全数据放磁盘文件排序的做法真的是相当的糟糕,性能其实会极差的。
如果我们用group by、union、distinct之类的语法的时候,万一你要是没法直接利用索引来进行分组聚合,那么他会直接基于临时表来完成,也会有大量的磁盘操作,性能其实也是极低 的。EXPLAIN SELECT x2, COUNT(/*) AS amount FROM t1 GROUP BY x2
这里的x2是没有索引的,所以此时的执行计划如下
这个SQL里只能对全表数据放到临时表里做大量的磁盘文件操作,然后才能完成对x2字段的不同的值去分组,分组完了以后对不同x2值的分组去做聚合操作,这个过程也是相当的耗时的,性能是极低的。
**记住:**在SQL调优的时候,核心就是分析执行计划里哪些地方出现了全表扫描,或者扫描数据过大,尽可能通过合理优化索引保证执行计划每个步骤都可以基于索引执行,避免扫描过多的数据。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/mingyuli/article/details/120809532
内容来源于网络,如有侵权,请联系作者删除!