分享幾個索引創建的小 Tips
關于 MySQL 中的索引,松哥前面已經和小伙伴們聊了不少了,不過在索引使用的時候,還是有一些需要注意的細節,如果忽略了這些細節,可能會讓索引的使用效果大打折扣。
1. 冗余索引
注意我這里使用了冗余索引,沒有使用重復索引,因為我覺得在小伙伴們使用索引的過程中,創建重復索引的概率應該還是比較小,同一個字段上創建多個一模一樣的索引,應該很少有人會犯這種錯誤。但是,會有一些容易被大家忽略的冗余索引,我們來捋一捋。
1.1 聯合索引左邊列
例如我創建了一個聯合索引 (A,B,C),按照我們之前跟大家講的最左匹配原則,當我們使用 A、(A、B)或者 (A、B、C)去查詢數據的時候,都會用到這個聯合索引,所以我們就沒有必要再去單獨針對 A 字段創建一個索引,或者針對 A、B 字段創建一個聯合索引。
1.2 索引中加入主鍵
假設我有一張表,該表有如下字段 (ID、A、B、C),其中 ID 是主鍵,現在又針對 A 和 ID 兩個字段創建了聯合索引(A、ID)。
根據松哥前面的介紹,小伙伴們知道,在二級索引中,葉子結點上存儲的數據就是 ID,所以,這個聯合索引中的 ID 字段顯然是多余的。
大部分情況下我們都不需要冗余索引,但是也有一些特殊情況可能讓我們不得不創建一些冗余索引,這個小伙伴們還是要具體問題具體分析。
另外需要注意一點,針對相同的字段,如果索引類型不同,則不能算是重復索引,例如一個普通索引和一個全文索引,同一個字段上同時有這兩個索引,不算重復索引。
2. 隱藏的索引排序
上篇文章松哥剛剛和大家聊了索引排序的問題。
結合上篇文章的內容,小伙伴們思考這樣一個問題:假設我有一張表,表中包含如下字段(ID、A、B),其中 ID 是主鍵,現在我針對 A 字段建立一個索引,如果我有如下查詢 SQL:
select ... from table where A=xxx order by ID由于在 A 這個二級索引中就包含了 ID 字段,所以上面這個查詢是可以使用到索引排序的。此時,如果由于其他需求,我們將 A 這個索引擴展成聯合索引(A、B)了,那么很明顯,再執行上面的查詢的時候就用不了索引排序了,只能 filesort 了。這樣的問題小伙伴們在創建或者修改索引的時候很容易忽略,所以一定要仔細。
3. 刪除不使用的索引
有的索引可能是由于過度考慮創建了,創建成功之后就沒用過,這樣的索引也應該刪除掉。
小伙伴們知道,索引雖然可以提高查詢速度,但是卻會降低插入和修改速度。
在 MySQL 的元數據庫 sys 中有一個名為 schema_unused_indexes 的視圖,該視圖中就保存了各種創建了但是未使用的索引:

4. 手動更新索引統計信息
當我們想要查看一條 SQL 的執行計劃時,這個執行計劃中會展示出來這個 SQL 執行過程中大概會掃描多少行數據,如下:

這個預估的掃描行數非常重要,這是 MySQL 優化器在執行 SQL 的時候一個重要的參考指標,如果表沒有這個統計信息,或者統計信息不準確,那么就有可能導致優化器做出錯誤的決定。
當滿足如下條件的時候,這個統計信息會自動生成或者更新:
- 首次打開表。
- 表大小發生變化。
- 執行 SHOW TABLE STATUS
- 執行 SHOW INDEX
- MySQL 客戶端開啟自動補全功能
- 打開 infomation_schema 庫中一些相關的表
這些行為都會觸發統計信息的自動更新,如果表中數據量比較大,擔心以上行為降低表的性能,那么也可以修改 innodb_stats_on_metadata 參數來關閉以上行為。

當然,我們也可以手動執行 analyze table 命令來更新索引的統計信息。

5. 適時優化表
InnoDB 中的索引是一個 B+Tree,這個我們在之前的文章中就和小伙伴們聊過了。B+Tree 通過一個多路平衡查找樹將數據組織在一起,然而這個樹中的各個結點在存儲的時候在物理分布上卻并不一定連續,如果是連續的,則在數據操作的時候就會快很多,如果不需連續,數據操作性能必然會有下降,一般來說,存在這樣幾種不同的碎片形式:
- 行碎片:數據行分布在不同的地方,讀取數據行的時候涉及到多次隨機 IO。
- 行間碎片:邏輯上應該是連續的行或者數據頁,在磁盤上存儲時并不連續。原本全表掃描的時候是順序 IO,現在變成了隨機 IO。
- 剩余空間碎片:小伙伴們知道,InnoDB 操作數據表最基本單位是頁,一頁是 16KB,也就是 InnoDB 從磁盤上讀、往磁盤上寫,最低單位都是 16KB,有時候這 16KB 中,有效數據很少,其他地方都是剩余空間,就會讓 InnoDB 在讀寫數據的時候造成很大浪費。
對于以上情況,我們可以通過執行 optimize table 來重新整理數據,如果存儲引擎不支持 optimize table 命令,那么我們也可以通過執行 alter table <table> engine=xxx 命令來實現數據的重整(命令中的 xxx 就是表原本的引擎)。

當然,optimize table 命令在執行的過程中還有一些細節問題,這個松哥后面再整文章和小伙伴們分享。























