Mysql 查詢優化之 Using filesort

最近在優化分頁查詢的時候,遇到了一個問題,如下(基於Mysql Innodb)
我們先建一個user表,其中有自增主鍵、user_id 也建立索引,create_date暫時不建索引,省略其他字段。
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '員工id',
  `create_date` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '創建日期',
   省略其他字段
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`)
當分頁查詢的時候,打印執行計劃,Extra 一欄出現了 Using filesort。
explain SELECT * FROM user ORDER BY create_date DESC limit 20.40;
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 22686 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+

 

Using filesort 是什麼意思?
官方的定義是,MySQL must do an extra pass to find out how to retrieve the rows in sorted order. The sort is done by going through  all rows according to the join type and storing the sort key and pointer to the row for all rows that match the WHERE clause . The keys then are sorted and the rows are retrieved in sorted order。
MySQL需要額外的一次傳遞,以找出如何按排序順序檢索行。通過根據聯接類型瀏覽所有行併爲所有匹配WHERE子句的行保存排序關鍵字和行的指針來完成排序。然後關鍵字被排序,並按排序順序檢索行。標紅,重點。
 
filesort 有兩種排序方式
  1. 對需要排序的記錄生成 <sort_key,rowid> 的元數據進行排序,該元數據僅包含排序字段和rowid。排序完成後只有按字段排序的rowid,因此還需要通過rowid進行回表操作獲取所需要的列的值,可能會導致大量的隨機IO讀消耗
  2. 對需要排序的記錄生成 <sort_key,additional_fields> 的元數據,該元數據包含排序字段和需要返回的所有列。排序完後不需要回表,但是元數據要比第一種方法長得多,需要更多的空間用於排序
 
優化方法
filesort 使用的算法是QuickSort,即對需要排序的記錄生成元數據進行分塊排序,然後再使用mergesort方法合併塊。其中filesort可以使用的內存空間大小爲參數 sort_buffer_size 的值,默認爲2M。當排序記錄太多 sort_buffer_size 不夠用時,mysql會使用臨時文件來存放各個分塊,然後各個分塊排序後再多次合併分塊最終全局完成排序。可以增大 sort_buffer_size 來解決 filesort 慢問題,也就是上面的第二種排序。
當  排序元組中的extra列的總大小不超過 max_length_for_sort_data 系統變量值的時候,我們如何優化 Using filesort 中的 回表操作 呢? 
文件排序優化不僅用於記錄排序關鍵字和行的位置,並且還記錄查詢需要的列。這樣可以避免兩次讀取行。
 
我們都知道,Mysql Innodb 下使用的是聚集索引。PRIMARY KEY 的葉子節點存儲的是數據,其他索引的葉子節點存儲的是PRIMARY KEY
 

 

當我們使用非PRIMARY KEY 查詢的時候,查詢1會進行回表操作,也就是額外的一次查詢,去查詢表中的其他數據,而查詢2會直接返回id和user_id。
查詢1:select * from user where user_id=1;
查詢2:select user_id,id from user where user_id=1;

 

好,下面我們通過例子來理解一下Using filesort 的形成
我們先來看第一條查詢,根據create_date 排序,由於create_date 沒有建索引,
explain SELECT create_date,id FROM user ORDER BY create_date DESC;
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 22686 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+

 

當使用沒有索引字段的時候,會出現 Using filesort。那我們建立 create_date  索引之後呢,看下結果。
explain SELECT create_date,id FROM user ORDER BY create_date DESC;
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 22686 | Using index    |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+

 

如果我想把 user_id 也查詢出來呢,看下結果。聚集索引,想查詢user_id 還是要進行回表操作的。
explain SELECT create_date,user_id FROM user ORDER BY create_date DESC;
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 22686 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+

 

 

歡迎關注公衆號,讓我們一起學習探討問題

 
回到開篇,我們如何優化這個查詢呢。
select * from (select id from user order by create_date DESC limit 20.40) a left join user b on a.id=b.id;
利用mysql聚集索引的性質,分頁查詢id,避免了Using filesort,這個查詢是很快的。而在分頁的數據量上,再去查詢所有數據,性能就很高了。
發佈了19 篇原創文章 · 獲贊 15 · 訪問量 9001
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章