本節說明 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
已建立索引。在此陳述式中,選取列表中的欄位名稱為 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 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 內部:追蹤優化器。