#### 8.2.1.16 优化排序

[](<>)[](<>)

本节描述 MySQL 何时可以使用索引来满足`订购方式`条款`文件排序`无法使用索引时使用的操作，以及可从优化器获得的执行计划信息`订购方式`.

一个`订购方式`有和没有`限制`可能会以不同的顺序返回行，如在[第 8.2.1.19 节，“限制查询优化”](limit-optimization.html).

-   [使用索引来满足 ORDER BY](order-by-optimization.html#order-by-index-use)

-   [使用文件排序来满足 ORDER BY](order-by-optimization.html#order-by-filesort)

-   [影响 ORDER BY 优化](order-by-optimization.html#order-by-optimizer-control)

-   [ORDER BY 执行计划信息可用](order-by-optimization.html#order-by-diagnostic-information)

##### 使用索引来满足 ORDER BY

在某些情况下，MySQL 可能会使用索引来满足`订购方式`子句并避免执行 a`文件排序`手术。

该索引也可以使用，即使`订购方式`与索引不完全匹配，只要索引的所有未使用部分和所有额外的`订购方式`列是常量`在哪里`条款。如果索引不包含查询访问的所有列，则仅当索引访问比其他访问方法便宜时才使用索引。

假设有一个索引`(*`key_part1`*, *`key_part2`*)`，以下查询可能会使用索引来解析`订购方式`部分。如果还必须读取不在索引中的列，优化器是否真的这样做取决于读取索引是否比表扫描更有效。

-   在此查询中，索引`(*`key_part1`*, *`key_part2`*)`使优化器避免排序：

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

    但是，查询使用`选择 *`，它可能选择的列多于*`key_part1`*和*`key_part2`*.在这种情况下，扫描整个索引并查找表行以查找不在索引中的列可能比扫描表和排序结果更昂贵。如果是这样，优化器可能不使用索引。如果`选择 *`仅选择索引列，使用索引并避免排序。

    如果`t1`是一个`InnoDB`表，表的主键隐含地是索引的一部分，索引可以用来解析`订购方式`对于这个查询：

    ```
    SELECT pk, key_part1, key_part2 FROM t1
      ORDER BY key_part1, key_part2;
    ```

-   在这个查询中，*`key_part1`*是常量，因此通过索引访问的所有行都在*`key_part2`*顺序和索引`(*`key_part1`*, *`key_part2`*)`避免排序，如果`在哪里`子句的选择性足以使索引范围扫描比表扫描便宜：

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

-   在接下来的两个查询中，是否使用索引类似于没有使用索引的相同查询`DESC`之前显示：

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

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

-   两列`订购方式`可以在同一方向排序（两者`ASC`， 或两者`DESC`）或相反的方向（一个`ASC`， 一`DESC`）。索引使用的一个条件是索引必须具有相同的同质性，但不必具有相同的实际方向。

    如果查询混合`ASC`和`DESC`，如果索引也使用相应的混合升序和降序列，则优化器可以在列上使用索引：

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

    优化器可以在 (*`key_part1`*,*`key_part2`*） 如果*`key_part1`*正在下降并且*`key_part2`*正在上升。它还可以在这些列上使用索引（使用向后扫描）*`key_part1`*正在上升并且*`key_part2`*正在下降。看[第 8.3.13 节，“降序索引”](descending-indexes.html).

-   在接下来的两个查询中，*`key_part1`*被比作一个常数。如果`在哪里`子句的选择性足以使索引范围扫描比表扫描便宜：

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

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

-   在下一个查询中，`订购方式`没有名字*`key_part1`*，但所有选择的行都有一个常数*`key_part1`*值，所以索引仍然可以使用：

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

    在某些情况下，MySQL*不能*使用索引来解决`订购方式`，尽管它可能仍然使用索引来查找与`在哪里`条款。例子：

-   查询使用`订购方式`在不同的索引上：

    ```
    SELECT * FROM t1 ORDER BY key1, key2;
    ```

-   查询使用`订购方式`在索引的非连续部分：

    ```
    SELECT * FROM t1 WHERE key2=constant ORDER BY key1_part1, key1_part3;
    ```

-   用于获取行的索引与`订购方式`：

    ```
    SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
    ```

-   查询使用`订购方式`使用包含索引列名称以外的术语的表达式：

    ```
    SELECT * FROM t1 ORDER BY ABS(key);
    SELECT * FROM t1 ORDER BY -key;
    ```

-   该查询连接了许多表，并且`订购方式`并非全部来自用于检索行的第一个非常量表。（这是第一个表[`解释`](explain.html)输出没有[`常量`](explain-output.html#jointype_const)加入类型。）

-   查询有不同`订购方式`和`通过...分组`表达式。

-   仅在`订购方式`条款。在这种情况下，索引不能用于完全解析排序顺序。例如，如果只有前 10 个字节[`字符(20)`](char.html)列被索引，索引无法区分超过第 10 个字节的值和`文件排序`是需要的。

-   索引不按顺序存储行。例如，对于一个`哈希`中的索引`记忆`桌子。

    用于排序的索引的可用性可能会受到使用列别名的影响。假设列`t1.a`被索引。在此语句中，选择列表中列的名称为`一种`.它指的是`t1.a`，正如对`一种`在里面`订购方式`, 所以索引`t1.a`可以使用：


```
SELECT a FROM t1 ORDER BY a;
```

在这个语句中，选择列表中的列名也是`一种`, 但它是别名。它指的是`ABS(一)`，正如对`一种`在里面`订购方式`, 所以索引`t1.a`不能使用：

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

在以下声明中，`订购方式`指的是不是选择列表中列名的名称。但是里面有一列`t1`命名为`一种`， 所以`订购方式`指`t1.a`和上的索引`t1.a`可以使用。（生成的排序顺序可能与`ABS(一)`， 当然。）

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

[](<>)[](<>)[](<>)[](<>)

以前（MySQL 5.7 及更低版本），`通过...分组`在特定条件下隐式排序。在 MySQL 8.0 中，不再发生，因此指定`按空排序`最后不再需要抑制隐式排序（如前所述）。但是，查询结果可能与以前的 MySQL 版本不同。要生成给定的排序顺序，请提供`订购方式`条款。

##### 使用文件排序来满足 ORDER BY

[](<>)[](<>)

如果一个索引不能用来满足一个`订购方式`子句，MySQL 执行一个`文件排序`读取表行并对它们进行排序的操作。一种`文件排序`在查询执行中构成一个额外的排序阶段。

获取内存`文件排序`操作，从 MySQL 8.0.12 开始，优化器根据需要增量分配内存缓冲区，直到由[`排序缓冲区大小`](server-system-variables.html#sysvar_sort_buffer_size)系统变量，而不是分配固定数量的[`排序缓冲区大小`](server-system-variables.html#sysvar_sort_buffer_size)前面的字节，就像在 MySQL 8.0.12 之前所做的那样。这使用户可以设置[`排序缓冲区大小`](server-system-variables.html#sysvar_sort_buffer_size)更大的值来加速更大的排序，而不用担心小排序会占用过多的内存。（Windows 上的多个并发排序可能不会出现这种好处，它具有弱多线程`malloc`.)

一种`文件排序`如果结果集太大而无法放入内存，则操作会根据需要使用临时磁盘文件。某些类型的查询特别适合完全在内存中`文件排序`操作。例如，优化器可以使用`文件排序`为了有效地在内存中处理，没有临时文件，`订购方式`以下形式的查询（和子查询）的操作：

```
SELECT ... FROM single_table ... ORDER BY non_index_column [DESC] LIMIT [M,]N;
```

此类查询在仅显示较大结果集中的几行的 Web 应用程序中很常见。例子：

```
SELECT col1, ... FROM t1 ... ORDER BY name LIMIT 10;
SELECT col1, ... FROM t1 ... ORDER BY RAND() LIMIT 15;
```

##### 影响 ORDER BY 优化

对于慢`订购方式`查询哪个`文件排序`未使用，请尝试降低[`max_length_for_sort_data`](server-system-variables.html#sysvar_max_length_for_sort_data)将系统变量设置为适合触发`文件排序`.（将此变量的值设置得太高的一个症状是磁盘活动高和 CPU 活动低的组合。）这种技术仅适用于 MySQL 8.0.20 之前。从 8.0.20 开始，[`max_length_for_sort_data`](server-system-variables.html#sysvar_max_length_for_sort_data)由于优化器更改使其过时且无效，因此已弃用。

增加`订购方式`速度，检查是否可以让 MySQL 使用索引而不是额外的排序阶段。如果这是不可能的，请尝试以下策略：

-   增加[`排序缓冲区大小`](server-system-variables.html#sysvar_sort_buffer_size)变量值。理想情况下，该值应该足够大，以使整个结果集适合排序缓冲区（以避免写入磁盘和合并通道）。

    考虑到存储在排序缓冲区中的列值的大小受[`最大排序长度`](server-system-variables.html#sysvar_max_sort_length)系统变量值。例如，如果元组存储长字符串列的值，并且您增加[`最大排序长度`](server-system-variables.html#sysvar_max_sort_length)，排序缓冲区元组的大小也会增加，可能需要您增加[`排序缓冲区大小`](server-system-variables.html#sysvar_sort_buffer_size).

    要监控合并通道的数量（合并临时文件），请检查[`Sort_merge_passes`](server-status-variables.html#statvar_Sort_merge_passes)状态变量。

-   增加[`read_rnd_buffer_size`](server-system-variables.html#sysvar_read_rnd_buffer_size)变量值，以便一次读取更多行。

-   更改[`临时目录`](server-system-variables.html#sysvar_tmpdir)系统变量指向具有大量可用空间的专用文件系统。变量值可以列出以循环方式使用的多个路径；您可以使用此功能将负载分散到多个目录中。用冒号字符分隔路径 (`：`) 在 Unix 和分号字符 (`;`) 在 Windows 上。路径应命名位于不同文件系统中的目录*身体的*磁盘，而不是同一磁盘上的不同分区。

##### ORDER BY 执行计划信息可用

和[`解释`](explain.html)（看[第 8.8.1 节，“使用 EXPLAIN 优化查询”](using-explain.html))，你可以检查 MySQL 是否可以使用索引来解析一个`订购方式`条款：

-   如果`额外的`列[`解释`](explain.html)输出不包含`使用文件排序`, 使用索引和`文件排序`不执行。

-   如果`额外的`列[`解释`](explain.html)输出包含`使用文件排序`, 索引没有被使用并且一个`文件排序`被执行。

    此外，如果一个`文件排序`执行时，优化器跟踪输出包括`文件排序摘要`堵塞。例如：


```
"filesort_summary": {
  "rows": 100,
  "examined_rows": 100,
  "number_of_tmp_files": 0,
  "peak_memory_used": 25192,
  "sort_mode": "<sort_key, packed_additional_fields>"
}
```

`peak_memory_used`表示排序过程中任何一次使用的最大内存。这是一个高达但不一定与该值一样大的值[`排序缓冲区大小`](server-system-variables.html#sysvar_sort_buffer_size)系统变量。在 MySQL 8.0.12 之前，输出显示`排序缓冲区大小`相反，指示的值[`排序缓冲区大小`](server-system-variables.html#sysvar_sort_buffer_size).（在 MySQL 8.0.12 之前，优化器总是分配[`排序缓冲区大小`](server-system-variables.html#sysvar_sort_buffer_size)排序缓冲区的字节数。从 8.0.12 开始，优化器以增量方式分配排序缓冲区内存，从少量开始，并根据需要添加更多，最多[`排序缓冲区大小`](server-system-variables.html#sysvar_sort_buffer_size)字节。）

这`排序模式`value 提供有关排序缓冲区中元组内容的信息：

-   `<排序键，行ID>`：这表示排序缓冲区元组是包含原始表行的排序键值和行 ID 的对。元组按排序键值排序，行 ID 用于从表中读取行。

-   `<排序键，附加字段>`：这表示排序缓冲区元组包含排序键值和查询引用的列。元组按排序键值排序，列值直接从元组中读取。

-   `<sort_key，packed_additional_fields>`：与前面的变体一样，但附加的列紧密地打包在一起，而不是使用固定长度的编码。

[`解释`](explain.html)不区分优化器是否执行`文件排序`在记忆中。使用内存`文件排序`可以在优化器跟踪输出中看到。寻找`文件排序优先级队列优化`.有关优化器跟踪的信息，请参阅[MySQL 内部：跟踪优化器](https://dev.mysql.com/doc/internals/en/optimizer-tracing.html).
