文档首页
MySQL 9.0 参考手册
相关文档 下载本手册
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
手册页 (TGZ) - 258.2Kb
手册页 (Zip) - 365.3Kb
信息 (Gzip) - 4.0Mb
信息 (Zip) - 4.0Mb


MySQL 9.0 参考手册  /  ...  /  ORDER BY 优化

10.2.1.16 ORDER BY 优化

本节介绍 MySQL 何时可以使用索引来满足 ORDER BY 子句,以及当无法使用索引时使用的 filesort 操作,以及优化器提供有关 ORDER BY 的执行计划信息。

具有和没有 LIMITORDER BY 可能以不同的顺序返回行,如第 10.2.1.19 节,“LIMIT 查询优化”中所述。

使用索引来满足 ORDER BY

在某些情况下,MySQL 可以使用索引来满足 ORDER BY 子句,并避免执行 filesort 操作所需的额外排序。

即使 ORDER BY 与索引不完全匹配,也可以使用索引,只要索引的所有未使用部分和所有额外的 ORDER BY 列在 WHERE 子句中都是常量。如果索引不包含查询访问的所有列,则只有在索引访问比其他访问方法更便宜的情况下才使用索引。

假设在 (key_part1, key_part2) 上有一个索引,则以下查询可以使用该索引来解决 ORDER BY 部分。优化器是否实际这样做取决于读取索引是否比表扫描更有效(如果需要读取索引中不存在的列)。

  • 在此查询中,在 (key_part1, key_part2) 上的索引使优化器能够避免排序

    SELECT * FROM t1
      ORDER BY key_part1, key_part2;

    但是,该查询使用 SELECT *,这可能会选择比 key_part1key_part2 更多的列。在这种情况下,扫描整个索引并查找表行以查找索引中不存在的列可能比扫描表并对结果进行排序更昂贵。如果是这样,优化器可能不会使用索引。如果 SELECT * 只选择索引列,则使用索引并避免排序。

    如果 t1 是一个 InnoDB 表,则表主键隐式地是索引的一部分,并且可以使用该索引来解决此查询的 ORDER BY

    SELECT pk, key_part1, key_part2 FROM t1
      ORDER BY key_part1, key_part2;
  • 在此查询中,key_part1 是常量,因此通过索引访问的所有行都按 key_part2 顺序排列,并且在 WHERE 子句足够选择性以使索引范围扫描比表扫描更便宜的情况下,在 (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;
  • ORDER BY 中的两个列可以按相同方向排序(都是 ASC,或都是 DESC),也可以按相反方向排序(一个 ASC,一个 DESC)。索引使用的一个条件是索引必须具有相同的同质性,但不需要具有相同的方向。

    如果查询混合了 ASCDESC,则如果索引也使用相应的混合升序和降序列,优化器可以使用列上的索引

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

    如果 key_part1 是降序,而 key_part2 是升序,则优化器可以使用 (key_part1, key_part2) 上的索引。如果 key_part1 是升序,而 key_part2 是降序,它也可以使用这些列上的索引(使用反向扫描)。见第 10.3.13 节,“降序索引”.

  • 在接下来的两个查询中,key_part1 与常量进行比较。如果 WHERE 子句足够选择性以使索引范围扫描比表扫描更便宜,则使用索引

    SELECT * FROM t1
      WHERE key_part1 > constant
      ORDER BY key_part1 ASC;
    
    SELECT * FROM t1
      WHERE key_part1 < constant
      ORDER BY key_part1 DESC;
  • 在下一个查询中,ORDER BY 没有指定 key_part1,但所有选定的行都有一个常数 key_part1 值,因此仍然可以使用索引。

    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 key1_part1, key1_part3;
  • 用于获取行的索引与 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 输出中第一个没有 const 连接类型的表。)

  • 查询有不同的 ORDER BYGROUP BY 表达式。

  • 仅对 ORDER BY 子句中命名的列的前缀有索引。在这种情况下,索引无法完全解决排序顺序。例如,如果仅对 CHAR(20) 列的前 10 个字节进行索引,则索引无法区分第 10 个字节之后的的值,需要 filesort

  • 索引不按顺序存储行。例如,对于 MEMORY 表中的 HASH 索引,情况就是这样。

使用列别名可能会影响用于排序的索引的可用性。假设列 t1.a 被索引。在此语句中,select 列表中列的名称为 a。它指的是 t1.aORDER BY 中的 a 引用也是如此,因此可以使用 t1.a 上的索引。

SELECT a FROM t1 ORDER BY a;

在此语句中,select 列表中列的名称也是 a,但它是别名。它指的是 ABS(a)ORDER BY 中的 a 引用也是如此,因此无法使用 t1.a 上的索引。

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

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

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

以前(MySQL 8.4 及更低版本),GROUP BY 在某些条件下隐式排序。在 MySQL 9.0 中,这种情况不再发生,因此在末尾指定 ORDER BY NULL 以抑制隐式排序(如以前所做的那样)不再必要。但是,查询结果可能与之前的 MySQL 版本不同。要生成给定的排序顺序,请提供 ORDER BY 子句。

使用 filesort 来满足 ORDER BY

如果无法使用索引来满足 ORDER BY 子句,MySQL 将执行 filesort 操作,该操作读取表行并对其进行排序。 filesort 是查询执行中的一个额外的排序阶段。

为了获得 filesort 操作的内存,优化器根据需要增量分配内存缓冲区,直到达到 sort_buffer_size 系统变量指示的大小。这使用户能够将 sort_buffer_size 设置为更大的值以加快较大的排序速度,而不用担心对较小的排序使用过多的内存。(对于 Windows 上的多个并发排序,此好处可能不会出现,Windows 的多线程 malloc 很弱。)

如果结果集太大而无法放入内存,则 filesort 操作将根据需要使用临时磁盘文件。某些类型的查询特别适合完全在内存中进行 filesort 操作。例如,优化器可以使用 filesort 来有效地处理内存中的 ORDER BY 操作(而无需临时文件),以处理以下形式的查询(和子查询)

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 优化

要提高 ORDER BY 速度,请检查您是否可以让 MySQL 使用索引而不是额外的排序阶段。如果无法做到这一点,请尝试以下策略

  • 增加 sort_buffer_size 变量值。理想情况下,该值应该足够大,以便整个结果集都适合排序缓冲区(以避免写入磁盘和合并传递)。

    请注意,存储在排序缓冲区中的列值的的大小会受到 max_sort_length 系统变量值的影响。例如,如果元组存储长字符串列的值,并且您增加了 max_sort_length 的值,则排序缓冲区元组的大小也会增加,您可能需要增加 sort_buffer_size

    要监控合并传递的数量(以合并临时文件),请检查 Sort_merge_passes 状态变量。

  • 增加 read_rnd_buffer_size 变量值,以便一次读取更多行。

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

ORDER BY 执行计划信息

使用 EXPLAIN(参见 第 10.8.1 节,“使用 EXPLAIN 优化查询”),您可以检查 MySQL 是否可以使用索引来解决 ORDER BY 子句。

  • 如果 EXPLAIN 输出的 Extra 列不包含 Using filesort,则使用索引,并且不执行 filesort

  • 如果 EXPLAIN 输出的 Extra 列包含 Using filesort,则不使用索引,并且执行 filesort

此外,如果执行了 filesort,则优化器跟踪输出将包含一个 filesort_summary 块。例如

"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 指示在排序期间任何时间使用的最大内存。这是一个不超过但也不一定等于 sort_buffer_size 系统变量值的数值。(优化器会增量分配排序缓冲区内存,从少量开始,根据需要增加,直到达到 sort_buffer_size 字节。)

sort_mode 值提供有关排序缓冲区中元组内容的信息

  • <sort_key, rowid>:这表示排序缓冲区元组是包含排序键值和原始表行的行 ID 的对。元组按排序键值排序,行 ID 用于从表中读取行。

  • <sort_key, additional_fields>:这表示排序缓冲区元组包含排序键值和查询引用的列。元组按排序键值排序,列值直接从元组中读取。

  • <sort_key, packed_additional_fields>:与前面的变体类似,但附加列紧密地打包在一起,而不是使用固定长度编码。

EXPLAIN 无法区分优化器是否在内存中执行了 filesort。可以在优化器跟踪输出中看到使用内存中的 filesort。查找 filesort_priority_queue_optimization。有关优化器跟踪的信息,请参见 MySQL 内部:跟踪优化器.