MySQL索引講解

         前段時間組裏要求員工做一些技術分享,boss問我有沒有什麼可以講的,想了想自己工作中發現的一些問題,那就談談索引吧。部門數據庫主要用的MySQL,存儲引擎通常採用InnoDB,所以本文主要分析一下MySQL中的索引策略。

MySQL支持的索引類型

        雖然MySQL支持的索引非常豐富,但是目前談索引如果沒有特別說明,默認都是指BTREE類(B-/+Tree)索引,如InnoDB、MyISAM都支持BTREE型的索引,當然MyISAM表也支持空間索引(R-Tree)、文本索引。Memory引擎默認的索引類型是Hash索引,Hash索引適合內存型的存儲,當然InnoDB中一些高頻的查詢也會用Hash索引。

        由於索引是在MySQL的存儲引擎層而非服務層,所以不同的存儲引擎支持的索引類型也不同,即使同一種索引類型,不同的存儲引擎實現的策略也可能有很大差異。在此主要談談BTREE型的索引吧。

 

BTREE (B-/+Tree)

 關於數據結構層面上B-/+Tree的嚴格數學定義大家參考維基百科吧
 https://en.wikipedia.org/wiki/B+tree

 https://en.wikipedia.org/wiki/B-tree

 

索引爲什麼大多是BTREE

        由於目前數據庫的數據主要是存儲在磁盤上,而BTREE適合作磁盤文件系統的索引,當然固態硬盤這種介於磁盤和內存之間的存儲技術可能會使BTREE淡出歷史的舞臺。BTREE適合做文件系統索引是由磁盤的讀寫特性決定的,關於磁盤讀寫的具體細節就不詳細贅敘了,有很多相關博客和資料。

磁盤 & 內存

          由於磁盤的扇區型結構以及磁盤旋轉比磁頭尋道要容易得多,所以數據在磁盤上存儲和讀取有一定的連續性,以致磁盤更適合順序訪問。磁盤數據讀入內存時通常以頁爲基本單位,一頁的大小是固定的,通常爲4k或者8k字節, 即使需要的是磁盤上1字節的數據,磁盤置換到內存中的數據也是1頁。這也是爲什麼BTREE索引每個節點的大小都是頁大小的整數倍。

局部性原理

程序的運行遵循局部性原理。通常從空間和時間兩個維度講局部性。衆所周知,相信寫代碼的同學都有過這種體會,程序中最近使用過的變量在將來極有可能重新使用,這就是時間維度的局部性原理。類似的思路,最近訪問過某個地址,那麼將來很有可能訪問這個地址附近的地址,這個是空間局部性。數據訪問也遵循這種局部性原理。

 

這兩種特性決定了BTREE更適合作磁盤文件系統的索引。

B-Tree索引 VS.  B+Tree索引
從底層BTREE數據結構的嚴格數學定義上講,除了一些其他的細微差別,最主要的區別就是B+Tree葉節點是包含所有Key值的鏈表。這使二者實現索引的策略天差地別。

在此我將差別歸納以下幾點:(肯定不全面)

 

1.節點存儲和檢索

        B-Tree索引中節點存放Key值和數據地址;而B+Tree索引非葉節點只存放key值,所有Key值都在葉節點出現,並且頁節點存放Key值和主鍵ID(或者數據地址)。所以B-Tree匹配到鍵值後可以直接獲取數據地址,而B+Tree則必須檢索到樹的葉節點。這種特性使B-Tree的查詢性能看起來優於B+Tree,但是實際中這是不確定的。由於B-Tree節點中會記錄數據地址信息,而B+Tree非葉節點只存儲鍵不存儲數據地址,所以B-Tree一個節點中存儲的鍵值更少,以致相同數據量B-Tree索引樹的高度會更高。
 由於B+Tree頁節點存儲所有的Key值,所以B+Tree索引文件消耗的空間比B-Tree大得多,這一點在大表上顯得尤爲突出。

2. 調整代價

       索引的作用是提高查尋效率,然而數據庫的增刪改操作對索引有很大的影響。由於B-Tree索引中鍵分佈在所有節點中,所以增刪改數據可能會引起索引樹的調整。而B+Tree樹中,由於葉節點存儲着所有的鍵,增刪改操作直接在葉節點進行,直到需要調整樹時纔會由葉節點向上調整,調整的頻率比B-Tree小很多。

聚簇索引

聚簇索引不是一種單獨的索引類型,而是一種數據存儲方式。聚簇是指相鄰鍵值對應的數據行緊密的存儲在一起,也因此一個表最多只能有一個聚簇索引。

 

聚簇索引把相關的數據保存在一起大大提高了I/O密集型應用的性能,數據訪問通常更快,但也有很大的弊端。如插入速度依賴插入主鍵的順序,如果不按照主鍵順序插入會移動頁內數據,如果頁內數據密集甚至會頻繁的分頁,同時修改主鍵也會產生數據的移動。

InnoDB  vs  MyISAM

MySQL中InnoDB和MyISAM默認的都是B+Tree索引,但二者的索引策略有非常大的差異。
 建表時,InnoDB存儲引擎會默認構建一個主簇索引,通常是表的Primary Key,如果沒有指定Primary Key,InnoDB會選擇一個Unique Key作爲主簇索引,如果表沒有Unique Key, InnoDB會構建一個主鍵作爲主簇索引。

      InnoDB中主簇索引葉節點存儲着數據地址、事務版本等信息,而非主簇索引的葉節點只存儲主鍵ID,所以如果不是用主鍵作爲條件數據檢索時會首先在二級索引中的葉節點找到對應的主鍵ID,然後回溯到主簇索引進行檢索。這也是爲什麼以前版本的MySQL InnoDB查詢速度不如MyISAM的主要原因。MySQL6.0後對此優化不少,二者的查詢性能相當。

 

二者差異總結幾點如下:

1. 查詢:

     InnoDB二級索引(非聚簇索引)頁節點存放主鍵ID而非數據地址,需要回溯聚簇索引查詢,即檢索兩次索引。MyISAM索引葉節點直接存放數據地址,頁內爲了減少Key值大小,採用前綴壓縮技術。

2. 插入和主鍵變更:

     InnoDB插入數據依賴主鍵的插入順序,變更主鍵可能會引起數據移動或頁分裂;當主鍵插入順序隨機時,性能嚴重下降。MyISAM數據的存儲順序由插入數據的先後決定(或者理解爲隨機型插入),主鍵變更和普通鍵無異。

3.      頁分裂代價:

         由於頁分裂會使數據地址變更,InnoDB只需要調整聚簇索引葉節點中的數據地址,而MyISAM需要調整所以索引葉節點的地址,代價遠遠大於InnoDB。但是InnoDB非主鍵順序插入會提高頁分裂的概率,而MyISAM不會。

 

4. 事務支持:

 

         InnoDB支持事務包括(XA、savepoints),事務的版本信息在聚簇索引的葉節點上。而MyISAM不支持事務。

5. 其他功能性差異

         如count(*) 計算,InnoDB需要掃描頁節點(因爲事務版本控制),而MyISAM直接讀取計數變量。
         MyISAM支持文本索引,不支持外鍵。

 

         等等... (功能性的網上資料較多)

實例

         理解索引運行的一些基本原則有助於使用者在複雜的開發環境中做出正確的設計和判斷,結合我工作中發現的

一些問題,借公司的一個表簡單的分析一下吧。當然,這只是冰山一角。

      上圖是我們業務的一張非常重要的消費表,採用InnoDB存儲引擎。

        編號1中 ID類型是字符串型,且爲主鍵,所以爲InnoDB中的聚簇索引鍵值。但是令人意外的是該ID訂單編號是隨機的,

並不是有序或局部有序的。根據聚簇索引的插入特性,會頻繁的移動數據或者頁分裂。這對插入效率和磁盤空間利用率

以及查詢效率都是致命的。

        編號2中,一些‘類型和狀態’字段用32位的int,奢華至極啊。

        編號3中,訂單金額用decimal(8,2),不得不說非常‘耿直’。MySQL中decimal在數值型中運算和存儲都是最差的,

比float差一個檔次(參考高性能MySQL 第三版章節4.1.2)。這種貨幣用int/bigint存儲,業務邏輯上換成元.

         編號4中,索引也很有問題。igameId和iGameType有對應關係,gameId已經確定了遊戲類型。且聯合索引時igameId做

前綴使用的概率更高,即使聯合也應將iGameType放在後面。另外iStatus狀態(狀態枚舉爲3)做爲索引是直接商榷的,畢竟唯一性

太差(當然聯合索引中一些情況也是可以提高效率的)。

        由於文章內容主要是個人對索引原理的一些理解的總結,所以有些地方可能不夠嚴謹。希望對大家理解索引有所幫助,同時也歡迎大家多多交流討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章