MySQL 索引優化 Using where, Using filesort

被遺忘的區域發表於2020-10-26

用Explain分析SQL語句的時候,經常發現有的語句在Extra列會出現Using filesort,根據MySQL官方文件對他的描述:

引用
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.

中文手冊上翻譯的很彆扭:

引用
“Mysql需要額外的一次傳遞,以找出如何按排序順序檢索行,通過根據聯接型別瀏覽所有行併為所有匹配where子句的行儲存排序關鍵字和行的指標來完成排序,然後關鍵字被排序,並按排序順序檢索行。”

總的來說,Using filesort 是Mysql裡一種速度比較慢的外部排序,如果能避免是最好的了,很多時候,我們可以通過優化索引來儘量避免出現Using filesort,從而提高速度。

這裡舉個簡單的例子:

CREATE TABLE testing (
id int(10) unsigned NOT NULL auto_increment,
room_number int(10) unsigned NOT NULL default ‘0’,
PRIMARY KEY (id),
KEY room_number (room_number)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

寫個儲存過程askwan,插入10萬條測試資料

mysql> DELIMITER $$

DROP PROCEDURE IF EXISTS myDB.askwan
CREATE DEFINER=healthtest@% PROCEDURE askwan()
BEGIN
DECLARE v INT DEFAULT 1;
WHILE v < 100000 DO
INSERT INTO testing VALUES(v,v);
SET v = v + 1;
END WHILE;
END

mysql> DELIMITER ;

mysql> CALL askwan();
Query OK, 1 row affected (13.21 sec)

OK,資料準備好了,開始試驗。

由上面例子中建立的表資訊,我已經建立了兩個索引,一個主鍵id,一個room_number列索引
那現在來看一條SQL,

SELECT id FROM testing WHERE room_number=1000 ORDER BY id ;

分析一下

mysql> EXPLAIN SELECT id FROM testing WHERE room_number=1000 ORDER BY id ;
±—±------------±--------±-----±--------------±------------±--------±------±-----±----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
±—±------------±--------±-----±--------------±------------±--------±------±-----±----------------------------+
| 1 | SIMPLE | testing | ref | room_number | room_number | 4 | const | 1 | Using where; Using filesort |
±—±------------±--------±-----±--------------±------------±--------±------±-----±----------------------------+
1 row in set (0.00 sec)

出現了Using filesort,並且用到了room_number這列索引,但是,在這裡用到的索引是針對WHERE後面的room_number條件的,而最後面的排序是根據id來的,這就是手冊中說的,“額外的一次排序”!,於是就會出現Using filesort,根據我以前寫過的一文章,我再建立一個聯合索引 room_number_id

alter table testing add index room_number_id(room_number,id);
在來分析一下

mysql> EXPLAIN SELECT id FROM testing WHERE room_number=1000 ORDER BY id ;
±—±------------±--------±-----±---------------------------±---------------±--------±------±-----±-------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
±—±------------±--------±-----±---------------------------±---------------±--------±------±-----±-------------------------+
| 1 | SIMPLE | testing | ref | room_number,room_number_id | room_number_id | 4 | const | 1 | Using where; |
±—±------------±--------±-----±---------------------------±---------------±--------±------±-----±-------------------------+
1 row in set (0.00 sec)

現在Using filesort不見了。

總結一下:
1.一般有order by語句,在索引加得不當的情況下,都有可能出現Using filesort,這時候就要對SQL語句和索引進行優化了,但是,並不是說出現Using filesort就是個嚴重的問題,不是這樣的,此次舉的例子比較極端,幾乎不太可能出現這麼傻瓜的查詢,優化和不優化,要看它是不是影響了業務效能。
2. 從上面可以看到聯合索引,也可以叫多列索引,形如 key (‘A1’,‘A2’,‘A3’ ,‘A4’)等的,排序的思路一般是,先按照A1來排序,A1相同,然後按照A2排序,以此類推,這樣對於(A1),(A1,A2),(A1,A2,A3)的索引都是有效的,但是對於(A2,A3)這樣的索引就無效了。

需要了解MySQL 的特性:

一條 SQL 語句只能使用 1 個索引 (5.0-),MySQL 根據表的狀態,選擇一個它認為最好的索引用於優化查詢
聯合索引,只能按從左到右的順序依次使用
從上邊可以看到結合索引,也可以叫多列索引,形如 key (‘B1’,‘B2’,‘B3’ ,‘B4’)等的,排序的思路通常為,先按照B1來排序,B1相同,然後按照B2排序,以此類推,這樣對於(B1),(B1,B2), (B1,B2,B3)的索引都是有效的,可是對於(B2,B3)這樣的索引就無效了。
根據這個特性就可以解決問題:

user_id 和 item_id 是 2 個索引,我的語句中,MySQL 選擇了 user_id,那麼 item_id 的索引沒有起到任何用處,所以,當我要排序的時候,由於記錄數較多,記憶體中的排序 buffer 滿了,只能 Using filesort 進行外部排序,因此每次查詢要從磁碟讀取幾十 M 的資料,太慢了。

修改表結構,刪除 user_id 和 item_id 的 INDEX 索引,建立一個名為 user_item 的聯合 UNIQUE 索引,順序是先 user_id 後 item_id,再 EXPLAIN,這回只有 Using where 了。

相關文章