1、Mysql Query Optimizer
这个名称在前言部分我在Mysql的整体架构中介绍过,称为查询优化器;这个查询优化器在绝大多数的公司,是不会做任何修改和扩展的,因为业务不需要,大牛请不起等因素,也就除了阿里这些大厂变态到把这玩意改了自己用。
Mysql中有专门负责优化Select语句的优化器模块,主要功能是:通过计算分析系统中手记到的统计信息,为客户端请求的Query提供Mysql认为最优化的执行计划,认为最优的检索方式,但是不一定是DBA认为是最优的,所以这部分是调整起来最耗时间的。
当客户端向Mysql请求一条查询的时候,命令解析器会完成请求的分类,区别出是Select操作,并转发给Mysql Query Optimizer时,Optimizer首先会对整个Query进行优化,处理掉一些常量表达式的运算,直接换算成常量值,或者根据查询条件出现的顺序进行个位置交换,如去掉一些无用或者显而易见的条件,结构调整等。然后分析Query的Hint信息(如果存在的话),看显示Hint信息是不是可以完全确定Query的执行计划,如果没有Hint,或者Hint不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,最后得出执行计划,这个过程,需要了解的重点是,Optimizer解析的结果和我们预期的结果不一定一样!
那Mysql是如何把自己认为最优的结果反馈给程序员或者DBA的呢,进而让Mysql更好的优化?
2、Mysql常见的性能瓶颈
CPU方面:CPU在很繁忙的时候,一般是在数据装入内存或者从磁盘上读取数据的时候,Mysql得到的CPU处理的时间片,分配的线程就远远不够。
IO:磁盘的IO也发生在装入的数据远大于内存导致磁盘IO高,Sql就会很慢,哪怕是一个最简单的查询语句。
服务器性能瓶颈,可以通过top,free,iostat,vmstat看下服务器的当前状态,是否需要升级配置。
3、Explain执行计划
概念:使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的SQL 语句的,进而分析你的查询语句或是表结构的性能瓶颈。
Explain能干这些事情:
- 表的读取顺序
- 数据读取的操作类型
- 哪些索引可以使用
- 哪些索引实际被使用
- 表之间的引用
- 每张表有多少行被优化器查询
用法: Explain+SQL 语句
假设我有个部门表:tbl_emp
这些字段包含的什么意思呢,通过这些字段怎么能知道我当前的sql执行是什么样的,需不需要优化?怎么玩?
3.1、id
概念:select查询的序列号,包含一组数字,表示查询中执行的select子句或者操作表的顺序,重点:子句+操作顺序
场景:
id相同,执行顺序由上至下
比如这个sql:explain select t2.* from t1, t2, t3 where t1.id = t2.id and t1.id = t3.id and t1.other_column = '';
这个Sql你认为的from 顺序是t1,t2,t3,但是不然,Mysql自己认为的是t1,t3,t2这样的顺序,所以当id相同时,执行顺序由上至下!
id不同的时候,如果是子查询,id的序号递增,id值越大优先级越高,越先被执行
比如这个sql:看下select_type列,有个subquery是子查询的意思,id=2和3的子查询,序号递增,优先级越高越先被执行,所以执行顺序是t3, t1, t2;并且仔细看这个sql,子查询正常会优先被执行,里面的结果出来,才会执行外面的。
id既有相同,也有不同,同时存在的时候;永远是数字大的优先级最高,遇到相同id,自上而下顺序执行。
比如这个sql:因为执行计划中既有id相同,又有id不同,此时记住数字大的优先级最高,所以先执行id=2,对应的表是t3表,其实对于sql,这个刚好是内部的子查询先执行;然后对于id相同的两个id=1,顺序至上而下,先是table表为
,derived意思是衍生表的意思,后面的数字2表示从id=2这个表t3衍生出来的,仔细看sql,from的子查询,别名叫s1,这个是衍生表,来源于t3表,这样就好理解了,然后继续执行t2表,这就是执行顺序的分析。 所以这句话应该就可以明白Explain可以干的第一件事:
3.2、select_type
概念:查询的类型,主要是用于区别普通查询,联合查询,子查询等复杂查询;
有这些属性:simple,primary,subquery,derived,union,union result。
simple:简单的select查询,查询中不包含子查询或者union
primary:查询中若包含任何复杂的子部分,最外层查询则被标记为primary,通常理解为外层查询
subquery:在select或者where列表中包含了子查询
derived:在from列表中包含的子查询被标记为derived(衍生)Mysql会递归执行这些子查询,把结果放在临时表里。
union:若第二个select出现在union之后,则被标记为union,若union包含在from子查询,外层的select则被标记为derived。
union result:从union表获取结果的select。
所以Explain也起到这个作用:
3.3、table
这里就不说了,表示当前的表,或者是衍生表
3.4、type
概念:访问类型排列,显示查询使用了何种类型,最好到最差的状态依次是,
system > const > eq_ref > ref > range > index > All
还有一些我没标出来,目前我是基本没接触到除了上面这些之外的,用到再去了解吧。
如果发现ALL,且你的数据在百万级别以上,请你务必优化,因为ALL全表扫描!!但是你的记录只有10条,你说要不要?那没必要,不要为了优化而优化,形成过度优化。
system:表只有一行记录,等于系统表,这个开发中,别指望了。
const:表示通过索引一次就找到了,const用于比较primary key或者unique索引,因为这个只匹配一行数据,所以很快;比如将主键查询放在where后,就可以迅速的将该查询转为一个常量。
比如这个sql,t1表执行后,是const,因为id主键条件在where后面。
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或者唯一索引扫描。
比如这个sql,先查t2表,查到一条与t1.id记录相匹配的一条记录,这个时候,会走eq_ref,这里和const有点类似,但是又不一样,const着重强调主键索引,但是这个强调在某个表中,这个索引键id,其他表中只有一条记录可以匹配;比如查一个公司的ceo,只有一条记录匹配。
ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体。
比如这个sql:对表建立了复合索引,执行计划col1='ac'这个记录,和上面eq_ref不同的是,返回的是这个col列的所有等于ac的所有行,这个col的索引,是非唯一性索引,值不是唯一的,比如类似name='zhangsan'的人有很多行。
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等查询,这种范围扫描索引,比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一个点,不用扫描全部索引;
比如这个sql:查between and的范围,还有in某个范围,type的属性都为range。 index:Full Index Scan,index与ALL的区别为index类型只遍历索引树,这通常比ALL快,因为索引文件通常比数据文件小,也就是说虽然ALL和Index都是读全表,但是Index是从索引中读取的,而all是从硬盘中读取;需要遍历整个索引树,就像你翻字典,不像上面可以直接定位查字母,而是从A查到Z,一个一个字母索引查,直到查到对应的索引;
比如这个sql:id已经是索引,查t1表的所有id字段,是不是要遍历整个索引表。
ALL:Full Table Scan,将遍历全表以找到匹配的行。
最明显的sql,什么索引都没有,select * from t1; 几百万甚至上亿的数据,直接裂开!
一般来说,保证查询至少到达range级别,最好能达到eq_ref。
3.5、possible_keys和key
概念:possible_keys显示可能应用在这张表上的索引,一个或者多个,查询涉及到的字段若存在索引,则该索引会被列出,但是不一定被查询使用!!!
key是实际在Mysql自己认为这条sql,真正实际用到的索引是什么,如果为NULL,则没有使用索引,若查询中使用了覆盖索引,则该索引仅出现在key列表中!
比如像这种理论上用到了2个索引,实际上只用到一个索引的情况也很常见。
什么是覆盖索引呢?
来看个经典的场景:理论上没有用到索引,为NULL,但是实际上key用到这个索引idx_col1_col2,这个就描述了刚刚的话,若查询中使用了覆盖索引,则该索引仅出现在key列表中,简单说来,覆盖索引就是你查的字段个数,顺序和索引一模一样;查col1,col2,和建立当前这个复合索引的个数,顺序,一一对应,这个索引是
create index idx_col1_col2 on t1(col1, col2);
3.6、key_len
概念:表示索引中使用的字节数,可以通过该列计算查询中使用的索引长度,在不损失精确性的情况下,长度越短越好,key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_ken是根据表定义计算而得,不是通过表内检索出的。
有个例子,比如我要通过相亲的信息来找对象,目前我只知道性别,筛选了一批女性,这个时候索引长度假如为2,那不能是个女的就可以,我要身材好的,那我再加个索引,查出来了一批,这个时候索引长度为6,依次下去当我想要更多的信息,精度越精细,得到最后一个属于我的女人的时候,这个时候索引长度会很长,有点类似时间换空间的概念。
3.7、ref
概念:显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或者常量被用于查找索引列上的值。
什么意思呢?看这个图,t1先加载后,这个ref值是const常量,对应sql的条件是t1.other_column=一个常量值,t2和t3表示引用test库下的t1表下的ID字段;
3.8、rows
概念:根据表统计信息以及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
比如这个sql,t2表是没有索引的;看下这个where后面的t1.id = t2.id这个是eq_ref唯一性扫描,只有一行匹配,再看下t2.col1='ac';所以没建立索引的时候,一共要找640+1行才可以找到结果;
如果加了索引呢?给t2加了索引后,整个查找的行数变成了142+1,可想而知,索引加了后,需要读取的行越少,效果越好!
这也印证了上面最后一个Explain的点:
3.9、Extra
概念:包含不适合在其他列中显示但十分重要的信息;
Extra的值有这些:Using filesort,Using temporary,Using where,Using index
Using filesort:说明Mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,Mysql中无法利用索引完成的排序操作称为“文件排序”。
我一直有说,索引等于排序+查找,Mysql说你给我建立了索引,我按照你说的方式去建立楼层,索引,我原本要按照这个顺序,可是在某种情况下我却用不到你给的索引,那么我只能另起炉灶,把这些顺序自己内部再排序一次。开发中遇到这个filesort,几乎就很坑爹,甚至九死一生,查询巨慢基本可以导致功能不可用。
举个例子,我这里有个sql,已经让Mysql建立好索引,idx_col1_col2_col3,比如1楼2楼3楼这么建楼,查询中有col1,where条件也有col1='ac',1楼用到了,所以type=ref引用了索引,ref字段是个const因为用到了常量ac,但是这个Extra字段出现了Using filesort!!!
原因是这样的,不好意思,你给的索引Mysql在检索方面,已经部分使用到了,而在排序的order by col3,Mysql没有用到,而索引增加的是检索和排序的优化,排序为什么Mysql用不到,排序的时候最好遵守所建索引的个数和顺序!!所以问题出现在这,索引顺序是1.2.3,但是你条件和排序后面跟的是1,3,2断层了!
再对比个sql,order by后面是col2,col3,所以条件+排序整个的顺序和个数,刚好符合1楼,2楼,3楼这样的索引顺序;此时filesort没了,后者性能,高于前者!如果可以,尽快优化!
Using temporary:在filesort是九死一生,在这个temporary就更严重了,前者只是帮你内部排了个序,后者帮你建立了个内部的临时表!可能要为你处理一些数据先拷贝到临时表里再去折腾,再删除临时表!常见于排序order by和分组查询group by。
看个例子,这个sql索引一定用到,只要key这个字段不为NULL,group by后面直接到col2这个字段了,会有人问,哎前面的where查询不是有了col的条件吗,这个我后面会说,在索引中,范围查找会失效,那么就等价于前面的where col1 in...这个1楼失效了,你让我直接group by 2楼去分组,我当然就走不到索引上了,所以自然而然产生了filesort,还有temporary。
我们可以这么优化,取消了临时表的创建,临时表的存在,是非常耗性能的!!
Using index:这个指标是非常好的,表示相应的select操作中使用了覆盖索引,避免访问了表的数据行,效果不错,如果同时出现了Using where,表明索引被用来执行索引键值的查找,如果没有同时出现Using where,表示索引用来读取数据而非执行查找动作。
看看这个sql:哥们我建立的索引是1和2,你只查2说明你用到了部分索引 。如果同时出现了Using where,表明索引被用来执行索引键值的查找。
再看这个sql:如果没有同时出现Using where,表示索引用来读取数据而非执行查找动作。这个sql就是读取数据,没有where条件;
覆盖索引再完善下概念:查询的列要被所建的索引覆盖!!或者部分满足,比如我建立col1和col2,我只查1或者2,也是覆盖索引的一种!!
注意:如果要使用覆盖索引,一定要注意select列表中只列出需要的列,不可写select *,因为如果将所有的字段一起做索引,会导致索引过大,性能查询下降!!
这三个最重要的字段,是调优中最常见且最重要的,希望大家掌握!!其他不重要的我这里就不多阐述了,用到再查吧。
本章介绍了Explain的详细知识点,下一章节开始拿案例进行调优!!