本节介绍 MySQL 何时可以使用索引来满足 ORDER BY
子句,以及当无法使用索引时使用的 filesort
操作,以及优化器提供有关 ORDER BY
的执行计划信息。
具有和没有 LIMIT
的 ORDER BY
可能以不同的顺序返回行,如第 10.2.1.19 节,“LIMIT 查询优化”中所述。
在某些情况下,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_part1
和key_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
)。索引使用的一个条件是索引必须具有相同的同质性,但不需要具有相同的方向。如果查询混合了
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
是降序,它也可以使用这些列上的索引(使用反向扫描)。见第 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 BY
和GROUP BY
表达式。仅对
ORDER BY
子句中命名的列的前缀有索引。在这种情况下,索引无法完全解决排序顺序。例如,如果仅对CHAR(20)
列的前 10 个字节进行索引,则索引无法区分第 10 个字节之后的的值,需要filesort
。索引不按顺序存储行。例如,对于
MEMORY
表中的HASH
索引,情况就是这样。
使用列别名可能会影响用于排序的索引的可用性。假设列 t1.a
被索引。在此语句中,select 列表中列的名称为 a
。它指的是 t1.a
,ORDER 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
子句。
如果无法使用索引来满足 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
速度,请检查您是否可以让 MySQL 使用索引而不是额外的排序阶段。如果无法做到这一点,请尝试以下策略
增加
sort_buffer_size
变量值。理想情况下,该值应该足够大,以便整个结果集都适合排序缓冲区(以避免写入磁盘和合并传递)。请注意,存储在排序缓冲区中的列值的的大小会受到
max_sort_length
系统变量值的影响。例如,如果元组存储长字符串列的值,并且您增加了max_sort_length
的值,则排序缓冲区元组的大小也会增加,您可能需要增加sort_buffer_size
。要监控合并传递的数量(以合并临时文件),请检查
Sort_merge_passes
状态变量。增加
read_rnd_buffer_size
变量值,以便一次读取更多行。更改
tmpdir
系统变量以指向具有大量可用空间的专用文件系统。变量值可以列出几个以循环方式使用的路径;您可以使用此功能将负载分散到多个目录。在 Unix 上用冒号字符 (:
) 分隔路径,在 Windows 上用分号字符 (;
) 分隔路径。路径应该命名位于不同 物理 磁盘上的文件系统中的目录,而不是同一磁盘上的不同分区。
使用 EXPLAIN
(参见 第 10.8.1 节,“使用 EXPLAIN 优化查询”),您可以检查 MySQL 是否可以使用索引来解决 ORDER BY
子句。
此外,如果执行了 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 内部:跟踪优化器.