1. 程式人生 > >MySQL查詢優化之order by的優化

MySQL查詢優化之order by的優化

原文地址:https://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html

譯文:

8.2.1.14 ORDER BY 優化

本節描述MySQL何時可以使用索引來滿足order by子句,不能使用索引時使用的檔案排序操作,以及優化器提供的有關order by的執行計劃資訊

有和沒有limit的order by子句可能會返回不同排序的行,可以參考Section 8.2.1.17, “LIMIT Query Optimization”

使用索引來滿足ORDER BY

在一些情況下,MySQL可能會使用索引來滿足一個order by子句,而避免涉及到執行檔案排序操作的額外排序。

只要索引中所有未使用的部分和所有額外的ordre by列都是where子句中的常量,即使order by與索引不完全匹配,也可以使用索引。如果索引不包含查詢訪問的所有列,則僅當索引訪問比其他訪問方法便宜時才使用該索引。

假如在(key_part1,key_part2)上有一個索引,下面的查詢可能會使用索引來解析order by部分。優化器是否會這樣做取決於如果不在索引中的列也必須讀取的話,讀取索引是否比表掃描更有效。

    1)在這個查詢中,(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;

    2)在下面的查詢中,key_part1是常量,所以通過索引訪問的所有行都是key_part2順序,如果where子句具有足夠的選擇性,使得索引範圍掃描比表掃描更便宜,則(key_part1, key_part2)上的索引會避免排序:

  • SELECT * FROM t1 WHERE key_part1 = constant ORDER BY key_part2;

    3)在接下來的兩個查詢中,是否使用索引類似於先前沒有desc的相同查詢:

  • SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;
    
    SELECT * FROM t1 WHERE key_part1 = constant ORDER BY key_part2 DESC;

    4)在接下來的兩個查詢中,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;

    5)在下一個查詢中,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子句匹配的行。例子:

    1)查詢在不同的索引上使用order by:

  • SELECT * FROM t1 ORDER BY key1, key2;

    2)查詢在索引的非連續部分上使用order by:

  • SELECT * FROM t1 WHERE key2=constant ORDER BY key1_part1, key1_part3;

    3)查詢混合使用ASC和DESC:

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

    4)用於獲取行的索引與在order by中使用的索引不同:

  • SELECT * FROM t1 WHERE key2=constant ORDER BY key1;

    5)查詢使用帶有表示式的order by,該表示式包含索引列名以外的其他術語:

  • SELECT * FROM t1 ORDER BY ABS(key);
    SELECT * FROM t1 ORDER BY -key;

    6)查詢連線許多表,而且order by子句中的列不全是來自用於檢索行的第一個非常量表(即explain輸出結果中連線型別不是cost的第一個表);

    7)查詢中有不同的order by和group by表示式;

    8)只有在order by子句中指定的列的字首上才有索引。在這種情況下,索引不能用於完全解析排序順序。例如,如果只索引宣告為CHAR(20)列的前10個位元組,則索引無法區分超過第10位元組的值,此時就需要檔案排序;

    9)索引沒有按順序儲存行。例如,記憶體表中的雜湊索引就是這樣的;

排序索引的可用性可能會受到列別名的使用的影響。假設列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;

預設情況下,如果查詢中同時包含order by col1,col2,···,MySQL會對group bycol1,col2···分類。如果查詢中包含一個顯式order by子句,該子句包含相同的列列表,MySQL會在不造成任何速度損失的情況下把其優化掉,儘管排序仍然會發生。

如果查詢中包含group by,但我們希望避免對結果排序的開銷,則可以通過指定order by null來抑制排序。

示例:

  • INSERT INTO foo
    SELECT a, COUNT(*) FROM bar GROUP BY a ORDER BY NULL;

優化器仍然可以選擇使用排序來實現分組操作。order by null會抑制結果的排序,而不是事先通過分組操作來確定結果的排序。

    Note

  • 預設情況下,group by進行隱式排序(即,在沒有asc或desc標誌符的情況下,對group by後的列進行排序)。但是,不建議使用隱式排序(即在沒有asc或desc標誌符的情況下進行排序)或顯式排序(即在group by列中使用顯式asc或desc指示符)對group by後的列進行排序。想要生成給定的排序順序,最好使用order by子句。

使用檔案排序來滿足ORDER BY

如果不能使用索引來滿足order by子句,MySQL會執行讀取錶行並對結果進行排序的檔案排序操作。檔案排序構成查詢執行中的一個額外排序階段。

為了獲得用於檔案排序操作的記憶體,優化器預先給變數sort_buffer_size分配了一定數量的位元組。單個會話可以根據需要更改該變數的會話值,以避免過度使用記憶體,或者根據需要分配更多記憶體。

如果結果集太大而無法裝入記憶體,檔案排序操作將根據需要使用臨時磁碟檔案。某些型別的查詢特別適合完全在記憶體中的檔案排序操作。例如,優化器可以以如下形式使用檔案排序來有效地處理記憶體中的查詢(和子查詢)中的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優化的影響因素

對於不使用檔案排序的慢排序查詢,可以嘗試將系統變數max_length_for_sort_data降低到一個適合觸發檔案排序的值(將該變數的值設定得過高的一個症狀是高磁碟活動和低CPU活動的組合)。

為了增加order by的速度,檢查是否可以讓MySQL使用索引,而不是額外的排序階段。如果這是不可能的,嘗試以下策略:

    1)增大sort_buffer_size變數的值。理想情況下,該值應該足夠大,可以容納整個結果集(以避免寫到磁碟和合並傳遞),但是該變數的最小值也必須足夠大到可以容納15個元組(合併最多15個臨時磁碟檔案,並且每個檔案必須有至少一個元組的記憶體空間)。

要考慮到儲存在排序緩衝區中的列值的大小受max_sort_length系統變數值的影響。例如,如果元組儲存長字串列的值,並且增加了max_sort_length的值,那麼排序緩衝區元組的大小也會增加,sort_buffer_size可能也需要增加。對於作為字串表示式結果計算的列值(例如呼叫字串值函式的列值),檔案排序演算法不能告訴表示式值的最大長度,因此必須為每個元組分配max_sort_length個位元組。

要監視合併傳遞的數量(合併臨時檔案),可以檢查Sort_merge_passes狀態變數;

    2)增加read_rnd_buffer_size變數值,以便一次讀取更多行;

    3)將tmpdir系統變數更改為指向具有大量空閒空間的專用檔案系統。該變數值可以列出以迴圈方式使用的幾個路徑;可以使用此特性將負載分散到多個目錄中。在Unix上使用冒號字元(:)分隔路徑,在Windows上使用分號字元(;)分隔路徑。路徑應該命名位於不同物理磁碟上的檔案系統中的目錄,而不是同一磁碟上的不同分割槽。

可獲得的ORDER BY執行計劃資訊

使用explain(可以參考Section 8.8.1, “Optimizing Queries with EXPLAIN”),我們可以檢查MySQL是否使用了索引來解析order by子句:

    1)如果explain輸出結果中的Extra列不包含Using filesort,則使用索引,不執行檔案排序;

    2)如果explain輸出結果中的Extra列包含Using filesort,則執行檔案排序,不使用索引;

此外,如果執行檔案排序,優化器跟蹤的輸出包括一個filesort_summary塊。例如:

"filesort_summary": {
  "rows": 100,
  "examined_rows": 100,
  "number_of_tmp_files": 0,
  "sort_buffer_size": 25192,
  "sort_mode": "<sort_key, packed_additional_fields>"
}

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

    1)<sort_key, rowid>:這表明排序緩衝區元組是一對包含原始錶行的排序鍵值和行ID的對。元組按鍵值排序,行ID用於從表中讀取行。

    2)<sort_key, additional_fields>:這表示排序緩衝區元組包含查詢引用的排序鍵值和列。元組按鍵值排序,列值直接從元組讀取。

    3)<sort_key, packed_additional_fields>:與前面的變體類似,但是附加列是緊密地打包在一起的,而不是使用固定長度的編碼。

explain不會區分優化器是否在記憶體中執行檔案排序。在優化器跟蹤的輸出中可以看到在記憶體中檔案排序的使用。尋找filesort_priority_queue_optimization。關於優化器跟蹤的資訊,可以參考MySQL Internals: Tracing the Optimizer

PS:由於水平有限,譯文中難免會存在謬誤,歡迎批評指正。