1. 程式人生 > >17.MySQL優化ORDER BY 優化

17.MySQL優化ORDER BY 優化

介紹

本節描述MySQL何時可以使用索引來滿足ORDER BY子句,無法使用索引時使用的filesort操作,和優化器提供的有關ORDER BY的執行計劃資訊。

一個ORDER BY有和沒有 LIMIT可能以不同的順序返回行:

  • 使用索引來優化ORDER BY
  • 使用filesort來優化ORDER BY
  • 影響ORDER BY優化
  • 可用的執行計劃資訊收集

使用索引來優化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 and 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是有序的,並且(key_part1, key_part2)避免了排序,因為where條件有足夠的選擇性來執行ndex range scan,所以使用索引的代價比table scan的代價小。

      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,優化器可以使用一個和列相同的混合了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降序。

  • 在下面兩個查詢中,key_part1的條件是常量值,假如WHERE條件具有足夠的選擇性使用索引的範圍掃描比全表掃描的代價更小,那麼下面的SQL將使用索引

      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 = constant1這個條件,所以仍然使用索引:

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

在某些情況下,MYSQL無法使用索引解決ORDER BY的問題,但是假如可以通過WHERE條件減少查詢的數量,MYSQL依然會使用索引:

  • 下面這個查詢的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;
    
  • 一個包含很多表的JOIN操作,ORDER BY中的列並非全部來自用於檢索行的第一個非常量表。(在第一個表的EXPALIN輸出中,不會包含const這個join型別)。

  • 一個查詢包含不同的ORDER BY和GROUP BY表示式。

  • 在ORDER BY的條件中只有列的字首包含索引。在這種情況下,索引不能完全解決排序問題。舉例,在char(20)的欄位中只有前10個有索引,索引無法區分超過10個位元組的值,而且必須使用filesort。

  • 索引可能不按照順序儲存行。比如,在MEMORY表中的HASH索引。

列的別名可能影響在排序時的索引的使用。假如t1.a被定義為索引,在這個宣告中,select語句中的是a,他指代的是t1.a,和ORDER BY的引用一樣,所以這個t1.a的索引可以使用。

	SELECT a FROM t1 ORDER BY a;

在這個宣告中,查詢的列仍然是a但是他是ABS(a)的別名,然後ORDER BY a的意思是ORDER BY ABS(a),所以他無法使用索引:

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

下面這個例子中,ORDER BY中依賴的欄位並沒有在SELECT中的別名不相同,所以他仍然可以使用索引:

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

在以前的版本中,GROUP BY可能包含隱式排序,在MYSQL8.0中,這個已經不會再發生,因此,不再需要在末尾指定ORDER BY NULL來抑制隱式排序,但是現在的版本結果可能和以前版本不同,假如有需要請指定ORDER BY條件。

使用filesort優化ORDER BY

假如索引無法滿足ORDER BY的條件,MySQL會使用filesort操作讀取表中的行並進行排序。filesort是構成一個額外的階段在所執行的查詢中。

為了獲得filesort操作的記憶體,從MySQL 8.0.12開始,優化器根據需要逐步分配記憶體緩衝區,最大為sort_buffer_size設定的大小。而不是像以前的版本中,提前分配 sort_buffer_size 的指定大小的固定的記憶體緩衝區。這個引數 sort_buffer_size 可以設定更大的值來跑更大的排序,而不用擔心較小的排序佔用較大的記憶體。(對於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優化

對於一個沒有使用filesort的比較慢的ORDER BY 查詢,可以嘗試降低max_length_for_sort_data的值,用來適當的觸發filesort。(將這個值設定的過高的表現是過高的磁碟活動以及很低的CPU使用率。)

為了提高ORDER BY的速度,檢查是否可以讓MySQL使用索引而不是額外的排序階段。假如無法使用索引,可以嘗試以下方法:

  • 提高 sort_buffer_size 的值。理想情況下應該使值足夠大,讓所有的排序操作在記憶體中的排序緩衝區中進行(避免寫入磁碟和合並傳遞)。

  • 考慮到儲存在排序緩衝區中的列值的大小受max_sort_length系統變數值的影響。例如:如果元祖儲存的長字串的列的值,並且增加了max_sort_length,那麼排序緩衝區的元組也需要增加,並且可能需要提高sort_buffer_size的值。

    監控merge passes的數量(用於合併臨時檔案),並且檢查Sort_merge_passes變數的值。

  • 提高read_rnd_buffer_size的變數的值可以同時讀取更多的行數。

  • 將tmpdir系統變數更改為指向具有大量可用空間的專用檔案系統。這個變數可以同時迴圈使用多個路徑。您可以使用此功能將負載分散到多個目錄中。在UNIX中使用(:)分割,在WINDOWS使用(;)分割。這個路徑應該是本地多個不同的物理磁碟,而不是同一個磁碟的不同目錄。

可用的執行計劃資訊收集

在EXPLAIN,可以判斷ORDER BY是否使用了索引:

  • 在EXPLAIN的Extra的輸出中不包含 filesort ,那麼索引被使用並且沒有使用filesort。
  • 在EXPLAIN的Extra的輸出中包含 filesort ,那麼索引未被使用並且使用了filesort。

此外,如果filesort被使用,優化器的trace輸出包含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一樣大。在MySQL 8.0.12之前,輸出顯示sort_buffer_size,指示sort_buffer_size的值。(在MySQL 8.0.12之前,優化器總是為sort緩衝區分配sort_buffer_size位元組。從8.0.12開始,優化器逐步分配排序緩衝區記憶體,從較少的值並且慢慢增加,最多為sort_buffer_size位元組。)

sort_mode值提供有關排序緩衝區中元組內容的資訊:

  • <sort_key, rowid>: 這表示排序緩衝區元組是包含原始錶行的排序的key值和行rowid。通過key排序然後通過rowid查詢資料。
  • <sort_key, additional_fields>:這表示排序緩衝區元組是包含原始錶行的排序的key值和查詢引用的列。元組按排序鍵值排序,列值直接從元組中讀取。
  • <sort_key, packed_additional_fields>: 與前一個版本一樣,但是其他列緊密排列在一起,而不是使用固定長度編碼。

EXPLAIN不區分優化器是否在記憶體中執行了filesort。