工做經歷中,隨着業務數據長時間積累,Mysql的數據也稍微有必定的量,因而乎當時咱們進行一次服務端慢查詢大排查,肯定慢查詢屬於哪一個工程而且將其優化掉。我工程內也有一個,大致是MQ訂閱到的taskid,taskid關聯task的log表去找最近的一條記錄的時間,而後根據時間校驗是否放行作相應業務處理。我explain下,發現當時寫的時候,log表的taskid也沒有建索引,當log表的記錄積累起來後,這個查詢會顯得很慢,建索引後有立竿見影的效果,固然這只是一個很是簡單的場景。其實這裏還衍生出一個問題:當log表數據量過大時候修改表結構,會形成一段時間的鎖表。雖然有些方式能夠避免鎖表,可是「合理時機」建立索引仍是很重要的。知其然,要知其因此然,來看看索引的那些事兒。html
Mysql數據通常以文件的形式存儲在磁盤上,讀取數據時須要在磁盤上進行IO操做。當須要從磁盤讀取數據時,系統會將數據邏輯地址傳給磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,即肯定要讀的數據在哪一個磁道,哪一個扇區。爲了讀取這個扇區的數據,須要將磁頭放到這個扇區上方,爲了實現這一點,磁頭須要移動對準相應磁道,這個過程叫作尋道,所耗費時間叫作尋道時間,而後磁盤旋轉將目標扇區旋轉到磁頭下,這個過程耗費的時間叫作旋轉時間。mysql
當一個數據被用到時,其附近的數據也一般會立刻被使用。sql
因爲磁盤順序讀取的效率很高(不須要尋道時間,只需不多的旋轉時間),所以對於具備局部性的程序來講,預讀能夠提升I/O效率。預讀的長度通常爲頁(page)的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操做系統每每將主存和磁盤存儲區分割爲連續的大小相等的塊,每一個存儲塊稱爲一頁(在許多操做系統中,頁得大小一般爲4k),主存和磁盤以頁爲單位交換數據。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置並向後連續讀取一頁或幾頁載入內存中,而後異常返回,程序繼續運行。數據庫
上邊內容能夠看出磁盤IO很是耗時,因此索引歸根結的目的:減小耗時磁盤IO次數,提升獲取數據的性能。緩存
在使用InnoDB存儲引擎時,若是沒有特別的須要,請永遠使用一個與業務無關的自增字段做爲主鍵。從數據庫索引優化角度看,使用InnoDB引擎而不使用自增主鍵絕對是一個糟糕的主意。上文討論過InnoDB的索引實現,InnoDB使用匯集索引,數據記錄自己被存於主索引(一顆B+Tree)的葉子節點上。這就要求同一個葉子節點內(大小爲一個內存頁或磁盤頁)的各條數據記錄按主鍵順序存放,所以每當有一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,若是頁面達到裝載因子(InnoDB默認爲15/16),則開闢一個新的頁(節點)。若是表使用自增主鍵,那麼每次插入新的記錄,記錄就會順序添加到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁。以下圖所示:bash
這樣就會造成一個緊湊的索引結構,近似順序填滿。由於每次插入時也不須要移動已有數據,因此效率很高,也不會增長不少開銷在維護索引上。若是使用非自增主鍵(若是身份證號或學號等),由於每次插入主鍵的值近似於隨機,因此每次新紀錄都要被插到現有索引頁得中間某個位置, 此時MySQL不得不爲了將新記錄插到合適位置而移動數據,甚至目標頁面可能已經被回寫到磁盤上而從緩存中清掉,此時又要從磁盤上讀回來,這增長了不少開銷。只要能夠,請儘可能在InnoDB上採用自增字段作主鍵。假設當前聯合索引爲:KEY a_id_state_index
(a_id
,name
) 查詢驗證:性能
EXPLAIN SELECT * FROM `user` WHERE `a_id` = 5 AND `name` = 'cj_25'
EXPLAIN SELECT * FROM `user` WHERE `name` = 'cj_25' AND `a_id` = 5
複製代碼
注:上面兩句結果都以下圖,由於mysql會對where裏面的條件順序在查詢以前會被mysql自動優化優化
EXPLAIN SELECT * FROM `user` WHERE `a_id` = 5
複製代碼
EXPLAIN SELECT * FROM `user` WHERE `name` = 'cj_25'
複製代碼
爲了分析仍是根據數據,按照上面規則去畫這顆B+樹:
對於輔助索引,樹的構建是按照「最左」字段的順序構建的,當查詢name時,只能順序查找,沒法使用二分。