MySQL提供了兩種排序算法操作(即ORDERBY操作,所以理解排序算法有助於理解ORDERBY優化),在查詢執行計劃中用“filesort”表示使用了外部文件進行排序。第一種算法是雙路排序算法,只利用ORDERBY子句中包括的列對象進行排序(適用於有BLOB、TEXT類型的列對象參與的排序);第二種算法是單路排序算法,除利用ORDERBY子句中包括的列對象外,還利用查詢目標列中的所有列對象進行排序(適用於除BLOB、TEXT類型外的所有的其他類型的排序)。
算法一:雙路排序算法的完整實現過程如下:
1) 按索引鍵或全表掃描的方式,讀取所有的元組,不匹配WHERE子句的元組被跳過;第一步需要從存儲讀入數據,引發I/O操作。
2) 對於每一行,在緩衝區中存儲一對值(對值,包括排序關鍵字和元組指針)。緩衝區的大小是系統變量的sort_buffer_size設定的值。
3) 當緩衝區已滿,運行快排算法(快速排序,qsort)對一個塊中的數據進行排序,將結果存儲在一個臨時文件。保存一個指向排序後的塊的指針(如果第二步所說的對值都能被緩衝區容納,則不會創建臨時文件)。
4) 重複上述步驟,直到所有的行已經被讀取。
5) 執行一個多路歸併操作(操作對象是第三步生成的每一個有序的塊)彙集到“MERGEBUFF域”,然後存放到在第二個臨時文件中。重複操作,直到第一個文件的所有塊歸併後存入到第二個文件;“MERGEBUFF域”是代碼sql_sort.h中定義的宏,值爲7。
6) 重複以下操作(第7步和第8步),直到留下少於“MERGEBUFF2域”標明的塊數爲止;“MERGEBUFF2域”是代碼sql_sort.h中定義的宏,值爲15。
7) 在最後一次多路歸併操作中,把元組的指針(排序關鍵字的最後部分)寫入到一個結果文件。
8) 在結果文件中,按照排列的順序使用元組指針讀取元組(爲了優化這項操作,MySQL讀入元組指針進入一個大的塊,對塊中元組指針進行排序而不是直接對數據排序,然後再用有序的元組指針獲取元組到元組緩存,元組緩衝區的大小由read_rnd_buffer_size參數控制)。第8步需要從存儲讀入數據,引發I/O操作。
算法二:單路排序算法,改進算法一,減少一次I/O,需要增加緩衝區大小容納更多信息。其具體實現過程如下:
1) 獲取與WHERE子句匹配的元組。這一步需要從存儲讀入數據,引發I/O操作。
2) 對於每一個元組,記錄排序鍵值、行的位置值、查詢所需的列。這一步記錄更多內容,需要更大緩存,內存存儲一條元組的信息的長度比算法一的“對值”大許多,這可能引發排序速度問題(排序對象的長度變長,但是內存有限,所以就需把一次內存排序變爲多次,進而影響排序的速度),爲了控制這個問題,MySQL引入一個參數“max_length_for_sort_data”,如果這一步得到的元組長度大於這個值,則不使用算法二。需要MySQL的使用者特別注意的是,在排序中,如果存在“很高磁盤I/O和很低的CPU利用率”的現象,則需要考慮調整“max_length_for_sort_data”的大小以變更換排序算法。
3) 按照排序的鍵值,對元組(元組是第二步的結果)進行排序。
算法二直接從緩衝區中的排序的元組中獲取有序的列信息等(查詢的目的對象),而不是第二次訪問該表讀取所需的列。相比算法一減少一次I/O。
MySQL支持對於ORDERBY的優化,,下面我們通過具體示例說明。
首先讓我們做一些準備工作。創建表,命令如下:
CREATE TABLE t_o1 (a1 INT UNIQUE, b1 INT);
CREATE TABLE t_o2 (a2 INT UNIQUE, b2 INT);
示例1 在索引列上進行排序操作,MySQL支持利用索引進行排序優化。
在一個表的索引列上執行排序操作,查詢執行計劃如下:
mysql> EXPLAIN SELECT * FROM t_o1 ORDER BY a1;
+----+-------------+-------+------+------+----------------+
| id | select_type | table | type | key | Extra |
+----+-------------+-------+------+------+----------------+
| 1 | SIMPLE | t_o1 | ALL | NULL | Using filesort |
+----+-------------+-------+------+------+----------------+
1 row in set (0.00 sec)
從查詢執行計劃看,對錶進行了全表掃描,並進行了排序操作(Using filesort)。沒有利用索引對排序進行優化。
對索引列進行查詢,查詢執行計劃如下:
mysql> EXPLAIN SELECT a1 FROM t_o1 ORDER BY a1;
+----+-------------+-------+-------+------+-------------+
| id | select_type | table | type | key | Extra |
+----+-------------+-------+-------+------+-------------+
| 1 | SIMPLE | t_o1 | index | a1 | Using index |
+----+-------------+-------+-------+------+-------------+
1 row in set (0.00 sec)
從查詢執行計劃看,對錶進行了索引掃描,利用索引對排序進行優化。對比上一條SQL語句,不同之處在於目標列不同,本條SQL語句的目標列是索引列,不是表的全部字段。這說明MySQL支持利用索引消除排序的技術,但只限於目標列也是索引列。
示例2 排序下推,MySQL不支持。在非索引列上執行連接,然後排序,查詢執行計劃如下:
mysql> EXPLAIN EXTENDED SELECT * FROM t_o1, t_o2 WHERE b1=b2 ORDER BY b1;
+----+-------------+-------+------+-------+----------------------------------------------------+
| id | select_type | table | type | key | Extra |
+----+-------------+-------+------+-------+----------------------------------------------------+
| 1 | SIMPLE | t_o1 | ALL | NULL | Using temporary; Using filesort |
| 1 | SIMPLE | t_o2 | ALL | NULL | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------+-------+----------------------------------------------------+
2 rows in set (0.00 sec)
從查詢執行計劃看,在表t_o1上分別進行了全表掃描後與t_o2表進行了連接,然後用臨時文件完成排序。這表明MySQL不可以把排序下推到基表單執行,然後再連接,不支持排序下推優化技術。