促使這次探索的初衷還是因為要對一個定時腳本性能進行優化。
腳本有兩個指定狀態分別是status, latest_process_status,和一個超期時間expire_time進行限制。
按照我以前的習慣,直接給這一組字段建了一個聯合索引。寫成了 expire_time_status_latest_process_status (expire_time, status, latest_process_status)
以前只知道,聯合索引的第一個值會做單獨的索引,但是不知道先后順序不同也會導致索引檢查的順序不同。
建完之后想來查看一下語句究竟會按照何種套路執行,去搜了一下如何分析,看到了執行計划。使用了之后發現大概能知道有沒有用索引,有哪些備選索引,需要怎么掃描之類。 先來看個例子。
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: ec_customer_service partitions: NULL type: range possible_keys: expire_time_latest_process_status_status key: expire_time_latest_process_status_status key_len: 6 ref: NULL rows: 95172 filtered: 25.00 Extra: Using index condition
select_type: 查詢的類型,這歌例子里面是一個簡單查詢的列子,對應的還有復雜查詢,擁有自查詢的復雜查詢會顯示不止一列。
table: 預計會操作的表
type: 表示mysql 執行計划預測會如何去訪問表中的數據,這列有all, index, ref, const range, eq_ref, null 這7種。在這里顯示的ref代表的是索引掃描,而且會與某些值進行參照。經常見到的type還有 all 和 range,一個代表全表掃描,一個代表范圍查找,范圍查找一邊會用到><=類似這種范圍型的條件。
possible_keys: 可能會選擇的索引,如果有多個索引可用,這里會全部列出來。
key: 執行計划會使用的索引。
key_len: 索引長度。
ref: 這個我沒太明白。。應該是參照key有個什么關系。
rows: 預計會掃描多少行。
Extra: 特別需要提到的信息:
這一列包含的是不適合在其他列顯示的額外信息。mysql用戶手冊里記錄了大多數可以在這里出現的值。
常見的最重要的值如下。
“Using index”
此值表示mysql將使用覆蓋索引,以避免訪問表。不要把覆蓋索引和index訪問類型弄混了。
“Using where”
這意味着mysql服務器將在存儲引擎檢索行后再進行過濾,許多where條件里涉及索引中的列,當(並且如果)它讀取索引時,就能被存儲引擎檢驗,因此不是所有帶where子句的查詢都會顯示“Using where”。有時“Using where”的出現就是一個暗示:查詢可受益於不同的索引。
“Using temporary”
這意味着mysql在對查詢結果排序時會使用一個臨時表。
“Using filesort”
這意味着mysql會對結果使用一個外部索引排序,而不是按索引次序從表里讀取行。mysql有兩種文件排序算法,這兩種排序方式都可以在內存或者磁盤上完成,explain不會告訴你mysql將使用哪一種文件排序,也不會告訴你排序會在內存里還是磁盤上完成。
“Range checked for each record(index map: N)”
這個意味着沒有好用的索引,新的索引將在聯接的每一行上重新估算,N是顯示在possible_keys列中索引的位圖,並且是冗余的。
上面提到了一個索引覆蓋,這里說下,索引覆蓋無需訪問數據文件,直接讀取索引文件進行返回。速度快的飛起,這一點可以從查找行數rows字段看出來。
通過上面的例子我們可以看出來,這里我需要用到的信息是行數rows和key 看一下是用到了哪個索引,並且同時看下執行需要掃描多少行才可以。說一下,這里數據一共是20w行,可以看到在使用這個索引的情況下竟然執行計划要掃描近10w行數據,可見效率是不高的。
為什么我在建立了看似不錯的聯合索引,效率才這么低。分析上面的sql語句不難發現,其實索引起最大作用的不應該是expire_time這個只規划了一個范圍的字段,而是在status和latest_process_status上面,特別是status狀態,在判斷了多個狀態的情況,是非常消耗查詢時間的。我們不妨使用status做一個單索引來看一下開銷
mysql> explain select * from ec_customer_service where expire_time<'2017-01-11' and status=1 and latest_process_status in (1000, 1001, 1002, 1003, 1004,1005); +----+-------------+---------------------+------+---------------+--------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------------------+------+---------------+--------+---------+-------+------+-------------+ | 1 | SIMPLE | ec_customer_service | ref | status | status | 4 | const | 1003 | Using where | +----+-------------+---------------------+------+---------------+--------+---------+-------+------+-------------+
不難發現,居然只需要 掃描1003行就可以解決問題?快了近90倍。。 可見索引並不是把要查詢的字段都包含進去就可以了這么簡單,而是要更詳細的分析自己使用的語句到底哪個字段或者哪幾個字段做成目錄可以提升查詢效率。
如果我們同時建立好幾個相關字段索引,在查看執行計划的時候,在possible_keys字段可以看到這些相關索引被羅列出來。 我們還可以通過在where前使用 「force index (索引字段)」 來強制語句使用該索引,因為mysql語句在執行的時候,引擎並不總是會做出最優判斷,你可以自己通過調整和查看執行計划,或者別的參數設置再結合自己的經驗強制使用某個字段做搜索索引,從而提升搜索速度。
在這個例子中,我也嘗試建立了類似 status_latest_process_status_expire_time這種聯合索引,效果其實和status差不多,同樣需要掃描近1000個字段,提升不大。而且filtered字段的值還會提升到達接近50%,key_lenth字段也變得很長,因為數據量並非 特別大,我還沒有測試出filtered字段增大對性能帶來的影響,但是如果能使用一個或兩個索引字段就能搞定的事情,為什么要使用多個呢?
Reference:
http://blog.itpub.net/29773961/viewspace-1767044/ MySQL 執行計划explain詳解