MySQL查询优化之十-ORDER BY优化(ORDER BY Optimization)

MySQL查询优化之十-ORDER BY优化(ORDER BY Optimization)


如需转载请标明出处:http://blog.csdn.net/itas109
QQ技术交流群:129518033

环境:
MySQL版本:5.5.15
操作系统:windows

本文讨论ORDER BY优化(ORDER BY Optimization)。

本文介绍MySQL何时可以使用索引来满足ORDER BY子句,当不能使用索引时使用的文件排列算法,以及优化器中有关ORDER BY的执行计划信息。

主要内容:

  • 使用索引满足ORDER BY
  • 使用filesort优化
  • 原始filesort算法
  • 修改后的filesort算法
  • filesort算法的比较
  • 影响ORDER BY优化
  • ORDER BY执行计划信息可用

1.使用索引满足ORDER BY

在某些情况下,MySQL可以使用索引来满足ORDER BY子句,而无需执行额外的排序。

只要所有未使用的索引部分和所有额外的ORDER BY列都是WHERE子句中的常量,即使ORDER BY与索引完全不匹配,也可以使用索引。 以下查询使用索引来解决ORDER BY部分:

SELECT * FROM t1
  ORDER BY key_part1, key_part2;

SELECT * FROM t1
  WHERE key_part1 = constant
  ORDER BY key_part2;

SELECT * FROM t1
  ORDER BY key_part1 DESC, key_part2 DESC;

SELECT * FROM t1
  WHERE key_part1 = 1
  ORDER BY key_part1 DESC, key_part2 DESC;

SELECT * FROM t1
  WHERE key_part1 > constant
  ORDER BY key_part1 ASC;

SELECT * FROM t1
  WHERE key_part1 < constant
  ORDER BY key_part1 DESC;

SELECT * FROM t1
  WHERE key_part1 = constant1 AND key_part2 > constant2
  ORDER BY key_part2;

在某些情况下,MySQL不能使用索引来解析ORDER BY,尽管它仍然可以使用索引来查找匹配WHERE子句的行。 例子:

  • 查询在不同的索引上使用ORDER BY:
SELECT * FROM t1 ORDER BY key1, key2;
  • 查询在索引的不连续部分使用ORDER BY:
SELECT * FROM t1 WHERE key2=constant ORDER BY key_part1, key_part3;
  • 查询混合了ASC和DESC:
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
  • 用于读取行的索引与ORDER BY中使用的索引不同:
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
  • 查询使用ORDER BY的表达式,该表达式包含索引列名称以外的其他术语:
SELECT * FROM t1 ORDER BY ABS(key);
SELECT * FROM t1 ORDER BY -key;
  • 查询连接多个表,并且ORDER BY中的列不是全部来自用于检索行的第一个非常数表。 (这是EXPLAIN输出中没有常量联接类型的第一个表。)
  • 该查询具有不同的ORDER BY和GROUP BY表达式。
  • 只有ORDER BY子句中指定的列的前缀有一个索引。 在这种情况下,索引不能用于完全解决排序顺序。 例如,如果仅对CHAR(20)列的前10个字节进行索引,则索引无法区分超过第10个字节的值并需要一个文件夹。
  • 索引不按顺序存储行。 例如,对于MEMORY表中的HASH索引,这是正确的。

用于排序的索引的可用性可能受到使用列别名的影响。 假设列t1.a被索引。 在这个语句中,选择列表中的列名是a。 它引用t1.a,就像在ORDER BY中引用a一样,所以可以使用t1.a上的索引:

SELECT a FROM t1 ORDER BY a;

在此语句中,选择列表中列的名称也是a,但它是别名。 它引用ABS(a),就像在ORDER BY中引用a一样,所以t1.a上的索引不能被使用:

SELECT ABS(a) AS a FROM t1 ORDER BY a;

在以下语句中,ORDER BY引用的名称不是选择列表中列的名称。 但是在t1中有一列名为a,所以ORDER BY引用t1.a并且可以使用t1.a上的索引。 (当然,得到的排序顺序可能与ABS(a)的顺序完全不同。)

SELECT ABS(a) AS b FROM t1 ORDER BY a;

默认情况下,MySQL对所有GROUP BY col1,col2,…查询进行排序,就像您在查询中指定了ORDER BY col1,col2 …一样。 如果你包含一个包含相同列列表的明确的ORDER BY子句,MySQL会优化它,而不会有任何速度损失,尽管排序仍然存在。
如果查询包含GROUP BY,但您希望避免排序结果的开销,则可以通过指定ORDER BY NULL来禁止排序。 例如:

INSERT INTO foo
SELECT a, COUNT(*) FROM bar GROUP BY a ORDER BY NULL;

优化器仍然可以选择使用排序来实现分组操作。 ORDER BY NULL会禁止对结果进行排序,而不是通过分组操作来完成排序以确定结果。

注意:
默认情况下GROUP BY默认排序(也就是说,在没有ASC或DESC标识符的情况下),但不建议使用隐式GROUP BY排序。 要生成给定的排序顺序,请为GROUP BY列使用显式的ASC或DESC指示符,或者提供ORDER BY子句。 GROUP BY排序是一个MySQL扩展,可能会在未来版本中更改; 例如,可以让优化器以任何它认为最有效的方式排序分组,并避免排序开销。

2.使用filesort优化

MySQL有多种文件排序算法用于排序和检索结果。 原始算法只使用ORDER BY列。 修改的算法不仅使用ORDER BY列,而且使用查询引用的所有列。

优化器选择要使用的文件流算法。 除了涉及BLOB或TEXT列时,它通常使用修改的算法,在这种情况下,它使用原始算法。 对于每种算法,排序缓冲区大小都是sort_buffer_size系统变量值。

3.原始filesort算法

原始的filesort算法工作如下:
1) 根据键或表格扫描读取所有行。 跳过与WHERE子句不匹配的行。
2) 对于每一行,在排序缓冲区中存储由一对值(排序键值和行ID)组成的元组。
3) 如果所有对都适合排序缓冲区,则不会创建临时文件。 否则,当排序缓冲区变满时,在内存中对其执行快速排序并将其写入临时文件。 保存一个指向已排序块的指针。
4) 重复前面的步骤,直到读取所有行。
5) 将MERGEBUFF(7)区域多合并到另一个临时文件中的一个块。 重复,直到第一个文件中的所有块都在第二个文件中。
6) 重复以下操作,直到MERGEBUFF2(15)块的数量少于左侧。
7) 在最后的多重合并中,只有行ID(值对的最后一部分)被写入结果文件。
8) 使用结果文件中的行ID按排序顺序读取行。 为了优化这一点,请读入一大块行ID,对它们进行排序,然后使用它们按排序顺序将行读入行缓冲区。 行缓冲区大小是read_rnd_buffer_size系统变量值。 此步骤的代码位于sql / records.cc源文件中。

这种方法的一个问题是它读取两次行:一次在WHERE子句评估过程中,以及在对值对进行排序之后。 即使第一次连续访问这些行(例如,如果进行了表扫描),也是第二次随机访问这些行。 (排序键是有序的,但行位置不是。)

4.修改后的filesort算法

修改后的filesort算法包含一个优化,以避免两次读取行:它记录排序键值,但不是行ID,而是记录查询引用的列。 修改后的filesort算法如下所示:
1) 读取与WHERE子句匹配的行。
2) 对于每一行,在排序缓冲区中存储由排序键值和查询引用的列组成的元组。
3) 当排序缓冲区变满时,通过内存中的排序键值对元组进行排序,并将其写入临时文件。
4) 合并排序临时文件后,按排序顺序检索行,但直接从已排序的元组中读取查询所需的列,而不是再次访问表。

修改后的filesort算法使用的元组长度比原始算法使用的长度更长,并且排序缓冲区中的元素更少。 因此,额外的I / O可以使修改的方法变得更慢,而不是更快。 为避免减速,只有在排序元组中额外列的总大小不超过max_length_for_sort_data系统变量的值时,优化程序才会使用修改的算法。 (将此变量的值设置得太高的一个症状是高磁盘活动和低CPU活动的组合。)

5.filesort算法的比较

假设一个表t1有四个VARCHAR列a,b,c和d,并且优化器使用这个查询的filesort:

SELECT * FROM t1 ORDER BY a, b;

查询按a和b排序,但返回所有列,所以查询引用的列是a,b,c和d。 根据优化器选择哪种文件排列算法,查询执行如下:

对于原始算法,排序缓冲区元组具有以下内容:

(fixed size a value, fixed size b value,
row ID into t1)

优化器对固定大小的值进行排序。 排序后,优化器按顺序读取元组,并使用每个元组中的行ID从t1读取行以获取选择列表列值。

对于修改的算法,排序缓冲区元组具有以下内容:

(fixed size a value, fixed size b value,
a value, b value, c value, d value)

优化器对固定大小的值进行排序。 排序后,优化器按顺序读取元组,并使用a,b,c和d的值来获取选择列表值,而不再读取t1。

6.影响ORDER BY优化

对于未使用filesort的慢ORDER BY查询,请尝试将max_length_for_sort_data降至适合触发文件夹的值。

要提高ORDER BY速度,请检查您是否可以让MySQL使用索引而不是额外的分类阶段。 如果这不可行,您可以尝试以下策略:

  • 增加sort_buffer_size变量值。 理想情况下,值应该足够大,以便整个结果集适合排序缓冲区(以避免写入磁盘和合并过程),但最小值必须足够大以容纳15个元组。

考虑到排序缓冲区中存储的列值大小受max_sort_length系统变量值影响。 例如,如果元组存储了长字符串列的值,并增加了max_sort_length的值,排序缓冲区元组的大小也会增加,并且可能需要您增加sort_buffer_size。 对于由字符串表达式计算的列值(如调用字符串值函数的结果),filesort算法不能指示表达式值的最大长度,因此它必须为每个元组分配max_sort_length字节。

要监视合并通道的数量,请检查Sort_merge_passes状态变量。

  • 增加read_rnd_buffer_size变量值。

  • 通过声明列的大小,每列使用更少的RAM,因为它们需要保存存储在其中的值。 例如,如果值不超过16个字符,则CHAR(16)优于CHAR(200)。

  • 将tmpdir系统变量更改为指向具有大量可用空间的专用文件系统。 变量值可以列出以循环方式使用的多个路径; 您可以使用此功能将负载分散到多个目录中。 用Unix上的冒号字符(:)和Windows上的分号字符(;)分隔路径。 路径应为位于不同物理磁盘上的文件系统中的目录命名,而不是同一磁盘上的不同分区中的目录。

7.ORDER BY执行计划信息可用

使用EXPLAIN SELECT … ORDER BY,您可以检查MySQL是否可以使用索引来解析查询。 如果您在Extra列中看到使用filesort,则不能这样做。 Filesort使用类似于MEMORY存储引擎所使用的固定长度的行存储格式。 可变长度类型(如VARCHAR)使用固定长度进行存储。

如果文件已完成,EXPLAIN输出包括在Extra列中使用filesort。

具有和不具有LIMIT的ORDER BY可以以不同顺序返回行。


Reference:
https://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html


觉得文章对你有帮助,可以用微信扫描二维码捐赠给博主,谢谢!
微信
如需转载请标明出处:http://blog.csdn.net/itas109
QQ技术交流群:129518033

猜你喜欢

转载自blog.csdn.net/itas109/article/details/79386327