MySQL性能调优与架构设计--第八章MySQL数据库Query的优化

在MySQL中有一个专门负责优化SELECT语句的优化器模块,这就是我们本节将要重点分析的MySQLOptimizer,其主要的功能就是通过计算分析系统中收集的各种统计信息,为客户端请求的Query给出他认为最优的执行计划,也就是他认为最优的数据检索方式。


在分析MySQLOptimizer的工作原理之前,先了解一下MySQL的QueryTree。MySQL的QueryTree
是通过优化实现DBXP的经典数据结构和Tree构造器而生成的一个指导完成一个Query语句的请求所
需要处理的工作步骤,我们可以简单的认为就是一个的数据处理流程规划,只不过是以一个Tree的数据
结构存放而已。通过QueryTree我们可以很清楚的知道一个Query的完成需要经过哪些步骤的处理,
每一步的数据来源在哪里,处理方式是怎样的。在整个DBXP的QueryTree生成过程中,MySQL使用了
LEX和YACC这两个功能非常强大的语法(词法)分析工具。MySQLQueryOptimizer的所有工作都是基
于这个QueryTree所进行的。


当客户端向MySQL请求一条Query,到命令解析器模块完成请求分类区别出是SELECT并转发给
QueryOptimizer之后,QueryOptimizer首先会对整条Query进行,优化处理掉一些常量表达式的预
算,直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或者显而易见的条
件,结构调整等等。然后则是分析Query中的Hint信息(如果有),看显示Hint信息是否可以完全
确定该Query的执行计划。如果没有Hint或者Hint信息还不足以完全确定执行计划,则会读取所涉
及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划。


在分析如何优化MySQLQuery之前,我们需要先了解一下Query语句优化的基本思路和原则。一
般来说,Query语句的优化思路和原则主要提现在以下几个方面:
1. 优化更需要优化的Query;
2. 定位优化对象的性能瓶颈;
3. 明确的优化目标;
4. 从Explain入手;
5. 多使用profile
6. 永远用小结果集驱动大的结果集;
7. 尽可能在索引中完成排序;
8. 只取出自己需要的Columns;
9. 仅仅使用最有效的过滤条件;
10.尽可能避免复杂的Join和子查询;




优化更需要优化的Query:
那什么样的Query是更需要优化呢?对于这个问题我们需要从对整个系统的影响来考虑。什么
Query的优化能给系统整体带来更大的收益,就更需要优化。一般来说,高并发低消耗(相对)的
Query对整个系统的影响远比低并发高消耗的Query大。
其次,如果通过CPU方面消耗的比较,原理和上面的完全一样。只要让第一个Query稍微节省一
小块资源,就可以让整个系统节省出一大块资源,尤其是在排序,分组这些对CPU消耗比较多的操作中
尤其突出。




定位优化对象的性能瓶颈:
当我们拿到一条需要优化的Query之后,第一件事情是什么?是反问自己,这条Query有什么问
题?我为什么要优化他?只有明白了这些问题,我们才知道我们需要做什么,才能够找到问题的关键。
所以,在拿到一条需要优化的Query之后,我们首先要判断出这个Query的瓶颈到底是IO还是
CPU。到底是因为在数据访问消耗了太多的时间,还是在数据的运算(如分组排序等)方面花费了太多资
源?
一般来说,在MySQL5.0系列版本中,我们可以通过系统自带的PROFILING功能很清楚的找出一个Query的瓶颈所在。




从Explain入手:
因为只有Explain才能告诉你,这个Query在数据库中是以一个什么样的执行计划来实现的。




永远用小结果集驱动大的结果集:
其实这样的结果也非常容易理解,在MySQL中的Join,只有NestedLoop一种Join方式,也就是
MySQL的Join都是通过嵌套循环来实现的。驱动结果集越大,所需要循环的此时就越多,那么被驱动表
的访问次数自然也就越多,而每次访问被驱动表,即使需要的逻辑IO很少,循环次数多了,总量自然也
不可能很小,而且每次循环都不能避免的需要消耗CPU,所以CPU运算量也会跟着增加。所以,如果
我们仅仅以表的大小来作为驱动表的判断依据,假若小表过滤后所剩下的结果集比大表多很多,结果就
是需要的嵌套循环中带来更多的循环次数,反之,所需要的循环次数就会更少,总体IO量和CPU运算
量也会少。而且,就算是非NestedLoop的Join算法,如Oracle中的HashJoin,同样是小结果集
驱动大的结果集是最优的选择。




尽可能在索引中完成排序:
排序操作是非常消耗CPU的操作,当系统设置不当或Query取出的字段过多时,还可能照成MySQL放弃新的排序算法,采取老的两次IO获取方式。
尽量利用索引进行排序操作,主要是利用索引的有序性。获取后的数据就是有序的,就不需要进行排序操作。
mysql> explain select * from inn;
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-------------+
| 1 | SIMPLE | inn | index | NULL | idx_inn_a | 5 | NULL | 2 | Using index |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-------------+
1 row in set (0.03 sec)


mysql> explain select * from inn order by a;
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-------------+
| 1 | SIMPLE | inn | index | NULL | idx_inn_a | 5 | NULL | 2 | Using index |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-------------+
1 row in set (0.00 sec)


mysql> drop index idx_inn_a on inn;


mysql> explain select * from inn;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | inn | ALL | NULL | NULL | NULL | NULL | 2 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.00 sec)


mysql> explain select * from inn order by a;
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| 1 | SIMPLE | inn | ALL | NULL | NULL | NULL | NULL | 2 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)




只取出自己需要的Columns:
任何时候在Query中都只取出自己需要的Columns,尤其是在需要排序的Query中。
对于任何Query,返回的数据都是需要通过网络数据包传回给客户端,如果取出的Column越多,
需要传输的数据量自然会越大,不论是从网络带宽方面考虑还是从网络传输的缓冲区来看,都是一个浪
费。
如果是需要排序的Query来说,影响就更大了。在MySQL中存在两种排序算法,一种是在
MySQL4.1之前的老算法,实现方式是先将需要排序的字段和可以直接定位到相关行数据的指针信息取
出,然后在我们所设定的排序区(通过参数sort_buffer_size设定)中进行排序,完成排序之后再次
通过行指针信息取出所需要的Columns,也就是说这种算法需要访问两次数据。第二种排序算法是从
MySQL4.1版本开始使用的改进算法,一次性将所需要的Columns全部取出,在排序区中进行排序后直
接将数据返回给请求客户端。改行算法只需要访问一次数据,减少了大量的随机IO,极大的提高了带有
排序的Query语句的效率。但是,这种改进后的排序算法需要一次性取出并缓存的数据比第一种算法
要多很多,如果我们将并不需要的Columns也取出来,就会极大的浪费排序过程所需要的内存。在
MySQL4.1之后的版本中,我们可以通过设置max_length_for_sort_data参数大小来控制MySQL选择
第一种排序算法还是第二种排序算法。当所取出的Columns的单条记录总大小
max_length_for_sort_data设置的大小的时候,MySQL就会选择使用第一种排序算法,反之,则会选
择第二种优化后的算法。为了尽可能提高排序性能,我们自然是更希望使用第二种排序算法,所以在
Query中仅仅取出我们所需要的Columns是非常有必要的。




仅仅使用最有效的过滤条件:
很多人在优化Query语句的时候很容易进入一个误区,那就是觉得WHERE子句中的过滤条件越多
越好,实际上这并不是一个非常正确的选择。其实我们分析Query语句的性能优劣最关键的就是要让他
选择一条最佳的数据访问路径,如何做到通过访问最少的数据量完成自己的任务。




尽可能避免复杂的Join和子查询:
我们都知道,MySQL在并发这一块做的并不是太好,当并发量太高的时候,系统整体性能可能会急
剧下降,尤其是遇到一些较为复杂的Query的时候更是如此。这主要与MySQL内部资源的争用锁定控
制有关,如读写相斥等等。对于Innodb存储引擎由于实现了行级锁定可能还要稍微好一些,如果使用
的MyISAM存储引擎,并发一旦较高的时候,性能下降非常明显。所以,我们的Query语句所涉及到的
表越多,所需要锁定的资源就越多。也就是说,越复杂的Join语句,所需要锁定的资源也就越多,所
阻塞的其他线程也就越多。相反,如果我们将比较复杂的Query语句分拆成多个较为简单的Query语
句分步执行,每次锁定的资源也就会少很多,所阻塞的其他线程也要少一些。




充分利用Explain和Profiling:


Explain的使用:
MySQLQueryOptimizer通过我让们执行EXPLAIN命令来告诉我们他将使用一个什么样的执行计划来优化我们的Query。


◆ID:QueryOptimizer所选定的执行计划中查询的序列号;
◆Select_type:所使用的查询类型,主要有以下这几种查询类型
◇DEPENDENTSUBQUERY:子查询中内层的第一个SELECT,依赖于外部查询的结果集;
◇DEPENDENTUNION:子查询中的UNION,且为UNION中从第二个SELECT开始的后面所有SELECT,同样依赖于外部查询的结果集;
◇PRIMARY:子查询中的最外层查询,注意并不是主键查询;
◇SIMPLE:除子查询或者UNION之外的其他查询;
◇SUBQUERY:子查询内层查询的第一个SELECT,结果不依赖于外部查询结果集;
◇UNCACHEABLESUBQUERY:结果集无法缓存的子查询;
◇UNION:UNION语句中第二个SELECT开始的后面所有SELECT,第一个SELECT为PRIMARY
◇UNIONRESULT:UNION中的合并结果;
◆Table:显示这一步所访问的数据库中的表的名称;
◆Type:告诉我们对表所使用的访问方式,主要包含如下集中类型;
◇all:全表扫描
◇const:读常量,且最多只会有一条记录匹配,由于是常量,所以实际上只需要读一次;
◇eq_ref:最多只会有一条匹配结果,一般是通过主键或者唯一键索引来访问;
◇fulltext:
◇index:全索引扫描;
◇index_merge:查询中同时使用两个(或更多)索引,然后对索引结果进行merge之后再
取表数据;
◇index_subquery:子查询中的返回结果字段组合是一个索引(或索引组合),但不是一个
主键或者唯一索引;
◇rang:索引范围扫描;
◇ref:Join语句中被驱动表索引引用查询;
◇ref_or_null:与ref的唯一区别就是在使用索引引用查询之外再增加一个空值的查询;
◇system:系统表,表中只有一行数据;
◇unique_subquery:子查询中的返回结果字段组合是主键或者唯一约束;
◆Possible_keys:该查询可以利用的索引.如果没有任何索引可以使用,就会显示成null,这一
项内容对于优化时候索引的调整非常重要;
◆Key:MySQLQueryOptimizer从possible_keys中所选择使用的索引;
◆Key_len:被选中使用索引的索引键长度;
◆Ref:列出是通过常量(const),还是某个表的某个字段(如果是join)来过滤(通过key)
的;
◆Rows:MySQLQueryOptimizer通过系统收集到的统计信息估算出来的结果集记录条数;
◆Extra:查询中每一步实现的额外细节信息,主要可能会是以下内容:
◇Distinct:查找distinct值,所以当mysql找到了第一条匹配的结果后,将停止该值的查
询而转为后面其他值的查询;
◇FullscanonNULLkey:子查询中的一种优化方式,主要在遇到无法通过索引访问null
值的使用使用;
◇ImpossibleWHEREnoticedafterreadingconsttables:MySQLQueryOptimizer通过
收集到的统计信息判断出不可能存在结果;
◇Notables:Query语句中使用FROMDUAL或者不包含任何FROM子句;
◇Notexists:在某些左连接中MySQLQueryOptimizer所通过改变原有Query的组成而
使用的优化方法,可以部分减少数据访问次数;
◇Rangecheckedforeachrecord(indexmap:N):通过MySQL官方手册的描述,当
MySQLQueryOptimizer没有发现好的可以使用的索引的时候,如果发现如果来自前面的
表的列值已知,可能部分索引可以使用。对前面的表的每个行组合,MySQL检查是否可以使
用range或index_merge访问方法来索取行。
◇Selecttablesoptimized away:当我们使用某些聚合函数来访问存在索引的某个字段的
时候,MySQLQueryOptimizer会通过索引而直接一次定位到所需的数据行完成整个查
询。当然,前提是在Query中不能有GROUPBY操作。如使用MIN()或者MAX()的时
候;
◇Usingfilesort:当我们的Query中包含ORDERBY操作,而且无法利用索引完成排序操
作的时候,MySQLQueryOptimizer不得不选择相应的排序算法来实现。
◇Usingindex:所需要的数据只需要在Index即可全部获得而不需要再到表中取数据;
◇Usingindexforgroup-by:数据访问和Usingindex一样,所需数据只需要读取索引即
可,而当Query中使用了GROUPBY或者DISTINCT子句的时候,如果分组字段也在索引
中,Extra中的信息就会是Usingindexforgroup-by;
◇Usingtemporary:当MySQL在某些操作中必须使用临时表的时候,在Extra信息中就会
出现Usingtemporary。主要常见于GROUPBY和ORDERBY等操作中。
◇Usingwhere:如果我们不是读取表的所有数据,或者不是仅仅通过索引就可以获取所有需
要的数据,则会出现Usingwhere信息;
◇Usingwherewithpushedcondition:这是一个仅仅在NDBCluster存储引擎中才会出现
的信息,而且还需要通过打开ConditionPushdown优化功能才可能会被使用。控制参数
为engine_condition_pushdown。


///创建索引的作用
mysql> explain select * from inn where id = 1;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | inn | ALL | NULL | NULL | NULL | NULL | 2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | inn | 全表扫描 | NULL | NULL | NULL | NULL | 2 | 额外细节 |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+


1 row in set (0.00 sec)


mysql> create index inn_id on inn(id);
Query OK, 0 rows affected (0.17 sec)
Records: 0 Duplicates: 0 Warnings: 0


mysql> explain select * from inn where id = 1;
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------+
| 1 | SIMPLE | inn | ref | inn_id | inn_id | 4 | const | 1 | |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------+
| 1 | SIMPLE | inn | 索引 | 可以利用的索引|选择的索引 |被选索引键长度| const | 1 | |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------+
1 row in set (0.00 sec)


mysql> alter table inn modify `id` int(11) NOT NULL AUTO_INCREMENT;
Query OK, 0 rows affected (0.10 sec)
Records: 0 Duplicates: 0 Warnings: 0


mysql> drop index inn_id on inn;
Query OK, 0 rows affected (0.15 sec)
Records: 0 Duplicates: 0 Warnings: 0


mysql> explain select * from inn where id = 1;
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | inn | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | inn | 常量 | 可以利用的索引|选择的索引|被选索引键长度| const | 1 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
1 row in set (0.00 sec)










mysql> set profiling = 1;
Query OK, 0 rows affected (0.00 sec)


mysql> select * from inn where id = 1;
+------+----+
| a | id |
+------+----+
| 2 | 1 |
+------+----+
1 row in set (0.00 sec)


mysql> show profiles;
+----------+------------+--------------------------------+
| Query_ID | Duration | Query |
+----------+------------+--------------------------------+
| 1 | 0.00027275 | select * from inn where id = 1 |
+----------+------------+--------------------------------+
1 row in set (0.00 sec)


mysql> show profile cpu,block io for query 1;
+----------------------+----------+----------+------------+--------------+---------------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting | 0.000050 | 0.000000 | 0.000000 | NULL | NULL |
| checking permissions | 0.000005 | 0.000000 | 0.000000 | NULL | NULL |
| Opening tables | 0.000021 | 0.000000 | 0.000000 | NULL | NULL |
| System lock | 0.000009 | 0.000000 | 0.000000 | NULL | NULL |
| init | 0.000016 | 0.000000 | 0.000000 | NULL | NULL |
| optimizing | 0.000007 | 0.000000 | 0.000000 | NULL | NULL |
| statistics | 0.000010 | 0.000000 | 0.000000 | NULL | NULL |
| preparing | 0.000008 | 0.000000 | 0.000000 | NULL | NULL |
| executing | 0.000001 | 0.000000 | 0.000000 | NULL | NULL |
| Sending data | 0.000054 | 0.000000 | 0.000000 | NULL | NULL |
| end | 0.000003 | 0.000000 | 0.000000 | NULL | NULL |
| query end | 0.000003 | 0.000000 | 0.000000 | NULL | NULL |
| closing tables | 0.000006 | 0.000000 | 0.000000 | NULL | NULL |
| freeing items | 0.000079 | 0.000000 | 0.000000 | NULL | NULL |
| logging slow query | 0.000002 | 0.000000 | 0.000000 | NULL | NULL |
| cleaning up | 0.000001 | 0.000000 | 0.000000 | NULL | NULL |
+----------------------+----------+----------+------------+--------------+---------------+
16 rows in set (0.00 sec)






合理设计并利用索引:


在MySQL中,主要有四种类型的索引,分别为:B-Tree索引,Hash索引,Fulltext索引和R-Tree索引。


在Innodb中如果通过主键来访问数据效率是非常高的,而如果是通过SecondaryIndex来
访问数据的话,Innodb首先通过SecondaryIndex的相关信息,通过相应的索引键检索到LeafNode
之后,需要再通过LeafNode中存放的主键值再通过主键索引来获取相应的数据行。


MyISAM存储引擎的主键索引和非主键索引差别很小,只不过是主键索引的索引键是一个唯一且非空的键而已。


Hash索引在MySQL中使用的并不是很多,目前主要是Memory存储引擎使用。


Full-text索引也就是我们常说的全文索引,目前在MySQL中仅有MyISAM存储引擎支持,而且也
并不是所有的数据类型都支持全文索引。目前来说,仅有CHAR,VARCHAR和TEXT这三种数据类型的列可
以建Full-text索引。




索引的利处:
“能够提高数据检索的效率,降低数据库的IO成本”。
降低数据的排序成本。
那如果是分组操作呢?分组操作没办法直接利用索引完成。但是分组操作是需要先进行排序然后才
分组的,所以当我们的Query语句中包含分组操作,而且分组字段也刚好和索引键字段一致,那么
mysqld同样可以利用到索引已经排好序的这个特性而省略掉分组中的排序操作。


◆较频繁的作为查询条件的字段应该创建索引;
◆唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件;
很多比较有经验的Query调优专家经常说,当一条Query所返回的数据超过了全表的15%的
时候,就不应该再使用索引扫描来完成这个Query了。
◆更新非常频繁的字段不适合创建索引;
◆不会出现在WHERE子句中的字段不该创建索引;




单键索引还是组合索引?
可以通过创建多个单键索引么?我们可以将WHERE子句中的每一
个字段都创建一个单键索引。但是这样真的有效吗?在这样的情况下,MySQLQueryOptimizer大多数
时候都只会选择其中的一个索引,然后放弃其他的索引。即使他选择了同时利用两个或者更多的索引通
过INDEX_MERGE来优化查询,可能所收到的效果并不会比选择其中某一个单键索引更高效。因为如果选
择通过INDEX_MERGE来优化查询,就需要访问多个索引,同时还要将通过访问到的几个索引进行merge
操作,所带来的成本可能反而会比选择其中一个最有效的索引来完成查询更高。
在一般的应用场景中,只要不是其中某个过滤字段在大多数场景下都能过滤出90%以上的数据,而且
其他的过滤字段会存在频繁的更新,一般更倾向于创建组合索引,尤其是在并发量较高的场景下更是
应该如此。因为当我们的并发量较高的时候,即使我们为每个Query节省很少的IO消耗,但因为执行
量非常大,所节省的资源总量仍然是非常可观的。


Query的索引选择:
在有些场景下,我们的Query由于存在多个过滤条件,而这多个过滤条件可能会存在于两个或者更
多的索引中。在这种场景下,MySQLQueryOptimizer一般情况下都能够根据系统的统计信息选择出一
个针对该Query最优的索引完成查询,但是在有些情况下,可能是由于我们的系统统计信息的不够准确
完整,也可能是MySQLQueryOptimizer自身功能的缺陷,会造成他并没有选择一个真正最优的索引而
选择了其他查询效率较低的索引。在这种时候,我们就不得不通过认为干预,在Query中增加Hint提
示MySQLQueryOptimizer告诉他该使用哪个索引而不该使用哪个索引,或者通过调整查询条件来达到
相同的目的。




MySQL中索引的限制:
1. MyISAM存储引擎索引键长度总和不能超过1000字节;
2. BLOB和TEXT类型的列只能创建前缀索引;
3. MySQL目前不支持函数索引;
4. 使用不等于(!=或者<>)的时候MySQL无法使用索引;
5. 过滤字段使用了函数运算后(如abs(column)),MySQL无法使用索引;
6. Join语句中Join条件字段类型不一致的时候MySQL无法使用索引;
7. 使用LIKE操作的时候如果条件以通配符开始('%abc...')MySQL无法使用索引;
8. 使用非等值查询的时候MySQL无法使用Hash索引;


Join语句的优化:
在MySQL中,只有一种Join算法,就是NestedLoopJoin,他没有其他很多数据库,NestedLoopJoin实际上就是通过驱动表
的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数
据,然后合并结果。如果还有第三个参与Join,则再通过前两个表的Join结果集作为循环基础数据,
再一次通过循环查询条件到第三个表中查询数据,如此往复。


Join语句的优化:
1. 尽可能减少Join语句中的NestedLoop的循环总次数;
2. 优先优化NestedLoop的内层循环;
3. 保证Join语句中被驱动表上Join条件字段已经被索引;
4. 当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝惜Join Buffer的设置;






ORDER BY,GROUP BY和DISTINCT优化:




ORDERBY的实现与优化:
在MySQL中,ORDERBY 的实现有如下两种类型:
◆一种是通过有序索引而直接取得有序的数据,这样不用进行任何排序操作即可得到满足客户端
要求的有序数据返回给客户端;
◆另外一种则需要通过MySQL的排序算法将存储引擎中返回的数据进行排序然后再将排序后的数
据返回给客户端。


在MySQL第二种排序实现方式中,必须进行相应的排序算法来实现数据的排序。MySQL目前可以通
过两种算法来实现数据的排序操作。
1. 取出满足过滤条件的用于排序条件的字段以及可以直接定位到行数据的行指针信息,在Sort
Buffer中进行实际的排序操作,然后利用排好序之后的数据根据行指针信息返回表中取得客户端请
求的其他字段的数据,再返回给客户端;
2. 根据过滤条件一次取出排序字段以及客户端请求的所有其他字段的数据,并将不需要排序的字
段存放在一块内存区域中,然后在SortBuffer中将排序字段和行指针信息进行排序,最后再利用
排序后的行指针与存放在内存区域中和其他字段一起的行指针信息进行匹配合并结果集,再按照顺
序返回给客户端。


当我们无法避免排序操作的时候,我们又该如何来优化呢?很显然,我们应该尽可能让MySQL选择
使用第二种算法来进行排序。这样可以减少大量的随机IO操作,很大幅度的提高排序工作的效率。
1. 加大max_length_for_sort_data参数的设置;
2. 去掉不必要的返回字段;
3. 增大sort_buffer_size参数设置;




GROUPBY的实现与优化:
由于GROUPBY实际上也同样需要进行排序操作,而且与ORDERBY相比,GROUPBY主要只是多了
排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函
数的计算。所以,在GROUPBY的实现过程中,与ORDERBY一样也可以利用到索引。
在MySQL中,GROUPBY的实现同样有多种(三种)方式,其中有两种方式会利用现有的索引信息
来完成GROUPBY。
1. 使用松散(Loose)索引扫描实现GROUPBY
实际上就是当MySQL完全利用索引扫描来实现GROUPBY的时候,并不需要扫描所有满足条件的索引键即可完成操作得出结果。
要利用到松散索引扫描实现GROUPBY,需要至少满足以下几个条件:
◆GROUPBY条件字段必须在同一个索引中最前面的连续位置;
◆在使用GROUPBY的同时,只能使用MAX和MIN这两个聚合函数;
◆如果引用到了该索引中GROUPBY条件之外的字段条件的时候,必须以常量形式存在;
Extra:Usingwhere;Usingindexforgroup-by


2. 使用紧凑(Tight)索引扫描实现GROUPBY
紧凑索引扫描实现GROUPBY和松散索引扫描的区别主要在于他需要在扫描索引的时候,读取所有
满足条件的索引键,然后再根据读取恶的数据来完成GROUPBY操作得到相应结果。
Extra:Usingwhere;Usingindex


3. 使用临时表实现GROUPBY
前面两种GROUPBY的实现方式都是在有可以利用的索引的时候使用的,当MySQLQuery
Optimizer无法找到合适的索引可以利用的时候,就不得不先读取需要的数据,然后通过临时表来完成
GROUPBY操作。
Extra:Usingwhere;Usingindex;Usingtemporary;Usingfilesort




对于上面三种MySQL处理GROUPBY的方式,我们可以针对性的得出如下两种优化思路:
1. 尽可能让MySQL可以利用索引来完成GROUPBY操作,当然最好是松散索引扫描的方式最佳。
在系统允许的情况下,我们可以通过调整索引或者调整Query这两种方式来达到目的;
2. 当无法使用索引完成GROUPBY的时候,由于要使用到临时表且需要filesort,所以我们必须
要有足够的sort_buffer_size来供MySQL排序的时候使用,而且尽量不要进行大结果集的GROUP
BY操作,因为如果超出系统设置的临时表大小的时候会出现将临时表数据copy到磁盘上面再进行
操作,这时候的排序分组操作性能将是成数量级的下降;






DISTINCT的实现与优化:
DISTINCT实际上和GROUPBY的操作非常相似,只不过是在GROUPBY之后的每组中只取出一条记
录而已。所以,DISTINCT的实现和GROUPBY的实现也基本差不多,没有太大的区别。同样可以通过松
散索引扫描或者是紧凑索引扫描来实现,当然,在无法仅仅使用索引即能完成DISTINCT的时候,MySQL
只能通过临时表来完成。但是,和GROUPBY有一点差别的是,DISTINCT并不需要进行排序。也就是
说,在仅仅只是DISTINCT操作的Query如果无法仅仅利用索引完成操作的时候,MySQL会利用临时表
来做一次数据的“缓存”,但是不会对临时表中的数据进行filesort操作。当然,如果我们在进行
DISTINCT的时候还使用了GROUPBY并进行了分组,并使用了类似于MAX之类的聚合函数操作,就无
法避免filesort了。

你可能感兴趣的:(MySQL性能调优与架构设计--第八章MySQL数据库Query的优化)