MySQL的優化主要分為結構優化(Scheme optimization)和查詢優化(Query optimization)。本章討論的高性能索引策略主要屬于結構優化范疇
本章的內容完全基于上文的理論基礎,實際上一旦理解了索引背后的機制,那么選擇高性能的策略就變成了純粹的推理,并且可以理解這些策略背后的邏輯。
示例數據庫
為了討論索引策略,需要一個數據量不算小的數據庫作為示例。本文選用MySQL官方文檔中提供的示例數據庫之一:employees。這個數據庫關系復雜度適中,且數據量較大。下圖是這個數據庫的E-R關系圖(引用自MySQL官方手冊):
圖12
MySQL官方文檔中關于此數據庫的頁面為http://dev.mysql.com/doc/employee/en/employee.html。里面詳細介紹了此數據庫,并提供了下載地址和導入方法,如果有興趣導入此數據庫到自己的MySQL可以參考文中內容。
最左前綴原理與相關優化
高效使用索引的首要條件是知道什么樣的查詢會使用到索引,這個問題和B+Tree中的“最左前綴原理”有關,下面通過例子說明最左前綴原理。
這里先說一下聯合索引的概念。在上文中,我們都是假設索引只引用了單個的列,實際上,MySQL中的索引可以以一定順序引用多個列,這種索引叫做聯合索引,一般的,一個聯合索引是一個有序元組,其中各個元素均為數據表的一列,實際上要嚴格定義索引需要用到關系代數,但是這里我不想討論太多關系代數的話題,因為那樣會顯得很枯燥,所以這里就不再做嚴格定義。另外,單列索引可以看成聯合索引元素數為1的特例。
以employees.titles表為例,下面先查看其上都有哪些索引:
從結果中可以到titles表的主索引為
這樣就可以專心分析索引PRIMARY的行為了。
情況一:全列匹配。
很明顯,當按照索引中所有列進行精確匹配(這里精確匹配指“=”或“IN”匹配)時,索引可以被用到。這里有一點需要注意,理論上索引對順序是敏感的,但 是由于MySQL的查詢優化器會自動調整where子句的條件順序以使用適合的索引,例如我們將where中的條件順序顛倒:
效果是一樣的。
情況二:最左前綴匹配。
當查詢條件精確匹配索引的左邊連續一個或幾個列時,如
情況三:查詢條件用到了索引中列的精確匹配,但是中間某個條件未提供。
此時索引使用情況和情況二相同,因為title未提供,所以查詢只用到了索引的第一列,而后面的from_date雖然也在索引中,但是由于 title不存在而無法和左前綴連接,因此需要對結果進行掃描過濾from_date(這里由于emp_no唯一,所以不存在掃描)。如果想讓 from_date也使用索引而不是where過濾,可以增加一個輔助索引
選擇性還不錯,但離0.9313還是有點距離,那么把last_name前綴加到4:
這時選擇性已經很理想了,而這個索引的長度只有18,比
此時再執行一遍按名字查詢,比較分析一下與建索引前的結果:
性能的提升是顯著的,查詢速度提高了120多倍。
前綴索引兼顧索引大小和查詢速度,但是其缺點是不能用于ORDER BY和GROUP BY操作,也不能用于Covering index(即當索引本身包含查詢所需全部數據時,不再訪問數據文件本身)。
InnoDB的主鍵選擇與插入優化
在使用InnoDB存儲引擎時,如果沒有特別的需要,請永遠使用一個與業務無關的自增字段作為主鍵。
經??吹接刑踊虿┛陀懻撝麈I選擇問題,有人建議使用業務無關的自增主鍵,有人覺得沒有必要,完全可以使用如學號或身份證號這種唯一字段作為主鍵。不論支持哪種論點,大多數論據都是業務層面的。如果從數據庫索引優化角度看,使用InnoDB引擎而不使用自增主鍵絕對是一個糟糕的主意。
上文討論過InnoDB的索引實現,InnoDB使用聚集索引,數據記錄本身被存于主索引(一顆B+Tree)的葉子節點上。這就要求同一個葉子節點內(大小為一個內存頁或磁盤頁)的各條數據記錄按主鍵順序存放,因此每當有一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,如果頁面達到裝載因子(InnoDB默認為15/16),則開辟一個新的頁(節點)。
如果表使用自增主鍵,那么每次插入新的記錄,記錄就會順序添加到當前索引節點的后續位置,當一頁寫滿,就會自動開辟一個新的頁。如下圖所示:
圖13
這樣就會形成一個緊湊的索引結構,近似順序填滿。由于每次插入時也不需要移動已有數據,因此效率很高,也不會增加很多開銷在維護索引上。
如果使用非自增主鍵(如果身份證號或學號等),由于每次插入主鍵的值近似于隨機,因此每次新紀錄都要被插到現有索引頁得中間某個位置:
圖14
此時MySQL不得不為了將新記錄插到合適位置而移動數據,甚至目標頁面可能已經被回寫到磁盤上而從緩存中清掉,此時又要從磁盤上讀回來,這增加了很多開銷,同時頻繁的移動、分頁操作造成了大量的碎片,得到了不夠緊湊的索引結構,后續不得不通過OPTIMIZE TABLE來重建表并優化填充頁面。
因此,只要可以,請盡量在InnoDB上采用自增字段做主鍵。