上节谈了关于mysql锁定机制的优化方案,下面来谈一下Query优化——Mysql Query Optimizer

当Mysql Query Optimizer接受到从Query Parser过来的Query时会根据相应语法进行分解分析,同时还会做很多其他的计算转化工作如常量转化,无效内容删除、常量计算等。所有这些工作都是为了Optimizer分析出最优的数据检索方式---执行计划。

Mysql Query Optimizer所有工作建立在Query Tree的基础上,QueryTree是通过优化实现DBXP的经典数据结构和Tree构造器而生成的,是指导一个完成一个Query语句的请求需要处理工作步骤,只是以Tree的数据结构存放而已。通过QueryTree可以很清楚的指导一个Query的完成需要经过哪些步骤,每一步的数据来源在哪里,处理方式是怎样的,在整个过程中,mysql使用了LEX和YACC这两个词法分析工具

Query语句优化基本思路:

1.优化更需要优化的query

2.定位优化对象的性能瓶颈

3.明确优化目标

4.从执行计划入手

5.多使用Profile

以上五点不是废话,只有清楚的知道了哪个地方是更需要优化的,性能瓶颈在什么地方,具体设计哪些对象,这样才能有效的优化,否则事倍功半。

Query优化原则:

1.永远使用小结果集驱动大结果集

2.尽可能在索引中完成排序

3.只取自己需要的列

4.仅仅使用最有效的过滤条件

5.尽可能避免复杂的join和子查询

解释一下以上五点。小结果集驱动,目的是减少循环,循环减少了,以为这被驱动表的IO减少了。索引中排序很好理解,所以是有顺序的,正是利用了这一点,不需要重新排序,否则性能会下降的很厉害。在一次查询中,很多人会取所有的列(select *),方便后续的扩展,并且他们认为多取得字段无非是占用网络的带宽造成延迟,可以忽略不计,其实最主要的影响是内存,mysql排序的算法是从原来的两次IO变成了一次,所有的排序等操作都是一次性取出来在排序区完成的,如果这时字段太多,内存势必会变大,当所占内存大到超过所设定的阈值,排序就会变成第一种算法,两次IO,性能会下降很多,所以尽量按需取值。where条件后并不是加的过滤条件越多越好,相反,如果索引太大会造成筛选的延迟,会造成性能的降低。mysql中所有的join都是嵌套查询,如果join复杂,会造成锁的对象多,高并发下延迟就大,造成性能的下跌,优化的方法是拆分查询,可以忽略多个查询的网络延迟和排队,牺牲一小部分性能让整理性能得以有较大幅度的提升。

索引

B+树:InnoDB存储引擎采用的是B+树存储数据,叶子节点存储数据或者数据地址,非叶子节点为索引值,叶子节点横向是一个链表,可以保证快速找到兄弟节点,快速遍历。

hash索引:基于哈希表实现,只有精确匹配到索引列的查询,才会起到效果。对于每一行数据,存储引擎都会对所有的索引列计算出一个哈希码(hash code),哈希码是一个较小的整数值,并且不同键值的行计算出来的哈希码也不一样。主要有Memory和NDB cluster存储引擎使用。实际上上通过一定的hash算法将需要索引的键值进行hash运算,然后将得到的hash值存入一个hash表,每次检索的时候使用相同的hash运算再和hash表中的hash值进行比较。在memory存储引擎中支持非唯一hash索引,相同的hash值将它们链接到同一个hash键值下,以一个链表的形式存在,检索的时候获取不同的键值过滤。由于键值的大小关系再经过hash运算后不能保证大小的顺序性,所以不能用于范围查询、排序操作,也不能利用组合索引中的部分索引功能。

Full-Text索引:仅MyISAM引擎支持。目前仅有CHAR、VARCHAR、TEXT支持Full-Text索引。主要用来替换效率低下的like。

索引优化:

1.较频繁的作为查询条件的字段应该创建索引

2.唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件

3.更新非常频繁的字段不适合创建索引

4.不会出现在where子句中的字段不该创建索引

Join实现与优化

 Join在Mysql中只有一种实现算法就是Nested Loop,通过驱动表的结果集作为循环基础数据,然后将该结果集中的数据作为过滤条件一条条的到下一个表中查询数据,最后合并结果,如果有第三张表,就用前两个表的结果作为查询条件,以此类推。

由此可以得出join优化思路:

1.尽可能减少Join语句中Nested loop的循环总次数

2.优先优化Nested loop的内层循环

3.保证join语句中被驱动表的join字段被索引

4.当无法join字段被索引,要设定较大的Join Buffer

Order by Group by Distinct优化

order by:有两种实现类型,第一种是通过索引直接取得有序的数据,直接返回客户端,运用了索引的有序性,另外一种是通过排序算法将存储引擎返回的数据排序后再返回。所以在优化的时候尽可能让索引覆盖排序字段,必要时可以添加索引字段。

第二种实现方式中mysql通过两种算法实现排序操作,第一种算法是取出过滤条件作为排序条件的字段,以及可以直接定位到行数据的行指针信息,在sort buffer中进行实际的排序操作,然后再利用排好序的数据根据行指针再去表中获取其他字段信息。

另外一种算法是一次性取出所有字段。将不需要排序的字段放在一块内存区域,然后再sort buffer中将排序字段和行指针信息进行排序,再和其他字段匹配得到结果。

优化:加大max_length_for_sort_data参数的设置、去掉不必要的返回字段、增大sort_buffer_size参数设置

group by:group by 实际上同样需要排序操作,而且与order by相比,groupby只不过是多了排序之后的分组,当然也可以在分组之后进行一些聚合函数计算,所以同样可以利用索引。

使用松散索引实现:这是最高效的方式,松散索引扫描读取的关键字数量与组数量一样多,可以比所有关键字数小得多。如果WHERE子句包含范围判断式, 松散索引扫描查找满足范围条件的每个组的第1个关键字,并且再次读取尽可能最少数量的关键字 。。有几个条件,group by字段必须处在同一个索引中的最前面的连续位置,只能使用max、min函数

使用紧凑索引实现:如果不满足松散索引扫描条件,GROUP BY查询仍然可以不用创建临时表。如果WHERE子句中有范围条件,该方法只读取满足这些条件的关键字。否则,进行索引扫描。该方法读取由WHERE子句定义的每个范围的所有关键字,或没有范围条件式扫描整个索引,我们将它定义为紧凑式索引扫描。

使用临时表实现:Group by如果想使用索引必须满足group by字段同时存放于同一个索引中,且是一个有序索引,还与是否使用聚合函数有关。无法使用索引时就不得不先读取所需数据,通过临时表来完成Group by操作

Distinct:和Group by非常类似,只是在group by分组之后每组只取出一条记录,没有聚合函数。