本節說明 MySQL 何時可以使用索引來滿足 ORDER BY
子句、當無法使用索引時所使用的 filesort
作業,以及從最佳化器取得的關於 ORDER BY
的執行計畫資訊。
如 第 10.2.1.19 節,「LIMIT 查詢最佳化」中所述,有和沒有 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_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.3 和更早版本),GROUP BY
在某些條件下會隱含排序。在 MySQL 8.4 中,不再發生這種情況,因此不再需要像之前那樣在末尾指定 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
系統變數值的影響。例如,如果 tuple 儲存長字串欄位的值,並且您增加max_sort_length
的值,則排序緩衝區 tuple 的大小也會增加,可能需要您增加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
值會提供關於排序緩衝區中 tuple 內容的資訊。
<sort_key, rowid>
:這表示排序緩衝區 tuple 是包含排序鍵值和原始資料表列的列 ID 的配對。Tuple 會依排序鍵值排序,而列 ID 用於從資料表讀取列。<sort_key, additional_fields>
:這表示排序緩衝區 tuple 包含排序鍵值和查詢參照的欄位。Tuple 會依排序鍵值排序,而欄位值會直接從 tuple 讀取。<sort_key, packed_additional_fields>
:與先前變體類似,但是額外的欄位會緊密地封裝在一起,而不是使用固定長度的編碼。
EXPLAIN
無法區分最佳化工具是否在記憶體中執行 filesort
。可以在最佳化工具追蹤輸出中看到使用記憶體中的 filesort
。尋找 filesort_priority_queue_optimization
。如需關於最佳化工具追蹤的資訊,請參閱MySQL 內部:追蹤最佳化工具。