一起學習Mysql索引一(索引的數據結構)

相信大家在使用Mysql的時候,爲了提高查詢效率或多或少的會使用到索引。然而,在建立索引的時候,你是根據什麼去創建索引對應的列的呢:每一個where查詢條件建立一條索引?根據查詢的列建立聯合索引?還是在多列索引中將選擇性最高的列放在第一列來建立索引?

如果我們不能夠對Mysql索引有更深的瞭解,可能就只能憑藉上面的一些經驗法則去建立索引,而結果可能並不能達到預期的效果。

那麼就和強哥一起學習Mysql索引,來提升自己對Mysql索引的理解吧!

索引的本質

索引(Index)是幫助MySQL高效獲取數據的數據結構,而在不同索引的數據結構之上,使用不同的查找算法,在不同的場景下便能產生不同的查詢效率。我們建立Mysql索引時,最長見的數據結構就是Hash和BTree。

Hash index

哈希索引基於哈希表實現。對於每一行數據,存儲引擎都會對所有的索引列計算一個hash code,不同鍵值的行計算出來的hash code也不一樣。哈希索引將所有的hash code存儲在索引中,同時在哈希表中保存指向每個數據行的指針。Mysql中的Memory引擎支持非唯一的哈希索引,這種情況下,其實現原理類似Java中HashMap的底層實現:如果多個列的hash code值相同,索引會以鏈表的方式存放多個記錄指針到同一個哈希條目中,先找到相同的hash code所在的鏈表後,在具體的比較找到最終的數據行。

因爲索引自身存儲對應的哈希值,所以索引的結構十分緊湊,這也讓哈希索引查找的速度非常快。然而,哈希索引也有他的限制:

  • 哈希索引數據不是按照索引順序存儲的,所以也就無法用於排序。

  • 哈希索引不支持部分索引匹配查找,因爲哈希索引始終是使用索引列的全部內容來計算哈希值的。

  • 哈希索引只支持等值比較查詢,包括=、IN()、<=>。

由於哈希索引的限制較多,所以我們一般建立索引的時候很少使用哈希索引,強哥這裏也就不再進行更詳細的說明,有興趣的同學可以自己去查詢相關的知識。

B-Tree和B+Tree

目前大部分數據庫系統及文件系統都採用B-Tree或其變種B+Tree作爲索引結構。

B-Tree

首先定義一條數據記錄爲一個二元組[key, data],key爲記錄的鍵值,對於不同數據記錄,key是互不相同的;data爲數據記錄除key外的數據。那麼B-Tree是滿足下列條件的數據結構:

  • d爲大於1的一個正整數,稱爲B-Tree的度。也就是說,可以建立d=2或者d=3,d=4等等不同度的B-Tree。

  • h爲一個正整數,稱爲B-Tree的高度。

  • 每個非葉子節點由n-1個key和n個指針組成,其中d<=n<=2d。

  • 每個葉子節點最少包含一個key和兩個指針,最多包含2d-1個key和2d個指針,葉節點的指針均爲null 。

  • 所有葉節點具有相同的深度,等於樹高h。

  • key和指針互相間隔,節點兩端是指針。

  • 一個節點中的key從左到右非遞減排列。

  • 所有節點組成樹結構。

  • 每個指針要麼爲null,要麼指向另外一個節點。

由於B-Tree的特性,在B-Tree中按key檢索數據的算法非常直觀:首先從根節點進行二分查找,如果找到則返回對應節點的data,否則對相應區間的指針指向的節點遞歸進行查找,直到找到節點或找到null指針,前者查找成功,後者查找失敗。B-Tree上查找算法的僞代碼如下:


BTree_Search(node, key) {
if(node == null) return null;
    foreach(node.key)
    {
        if(node.key[i] == key) 
          return node.data[i];
        if(node.key[i] > key) 
          return BTree_Search(point[i]->node, key);
    }
    return BTree_Search(point[i+1]->node, key);
}
data = BTree_Search(root, my_key);

一個度爲d的B-Tree,設其索引N個key,則其樹高度h的上限爲logd((N+1)/2),檢索一個key,其查找節點個數的漸進複雜度爲O(logdN)。

B+Tree

MySQL就普遍使用B+Tree實現其索引結構。與B-Tree相比,B+Tree有以下不同點:

  • 每個節點的指針上限爲2d而不是2d+1。

  • 內節點不存儲data,只存儲key;葉子節點不存儲指針。

一般在數據庫系統或文件系統中使用的B+Tree結構都在經典B+Tree的基礎上進行了優化,增加了順序訪問指針。

在B+Tree的每個葉子節點增加一個指向相鄰葉子節點的指針,就形成了帶有順序訪問指針的B+Tree。做這個優化的目的是爲了提高區間訪問的性能,例如上圖中如果要查詢key爲從18到49的所有數據記錄,當找到18後,只需順着節點和指針順序遍歷就可以一次性訪問到所有數據節點,極大提到了區間查詢效率。

爲什麼使用B+Tree作爲索引呢?

一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗,相對於內存存取,I/O存取的消耗要高几個數量級,所以評價一個數據結構作爲索引的優劣最重要的指標就是在查找過程中磁盤I/O操作次數的漸進複雜度。換句話說,索引的結構組織要儘量減少查找過程中磁盤I/O的存取次數。

根據B-Tree的定義,可知檢索一次最多需要訪問h個節點。數據庫系統的設計者巧妙利用了磁盤預讀原理(即使只需要一個字節,磁盤也會從這個位置開始,順序向後讀取一定長度的數據放入內存),將一個節點的大小設爲等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。爲了達到這個目的,在實際實現B-Tree還需要使用如下技巧:

每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁裏,加之計算機存儲分配都是按頁對齊的,就實現了一個node只需一次I/O。

B-Tree中一次檢索最多需要h-1次I/O(根節點常駐內存),漸進複雜度爲O(h)=O(logdN)。一般實際應用中,出度d是非常大的數字,通常超過100,因此h非常小(通常不超過3)。綜上所述,用B-Tree作爲索引結構效率是非常高的。

而B+Tree爲什麼比B-Tree更適合作爲索引呢?原因與內節點初度d有關。從上面分析可以看到,d越大索引的性能越好,而出度的上限取決於節點內key和data的大小:

dmax = floor(pagesize / (keysize + datasize + pointsize))dmax = floor(pagesize / (keysize + datasize + pointsize))

floor表示向下取整。由於B+Tree內節點去掉了data域,因此可以擁有更大的出度,擁有更好的性能。

而其他類似紅黑樹的結構,h明顯要深的多。由於邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性,所以紅黑樹的I/O漸進複雜度也爲O(h),效率明顯比B-Tree差很多。

Mysql索引實現

在MySQL中,索引屬於存儲引擎級別的概念,不同存儲引擎對索引的實現方式是不同的,接下來我們主要討論MyISAM和InnoDB兩個存儲引擎的索引實現方式。

MyISAM索引實現

MyISAM引擎使用B+Tree作爲索引結構,葉節點的data域存放的是數據記錄的地址。下圖是MyISAM索引的原理圖:

這裏設表一共有三列,假設我們以Col1爲主鍵,則上圖是一個MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重複。如果我們在Col2上建立一個輔助索引,則此索引的結構如下圖所示:

同樣也是一顆B+Tree,data域保存數據記錄的地址。因此,MyISAM中索引檢索的算法爲首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然後以data域的值爲地址,讀取相應數據記錄。MyISAM的索引方式也叫做“非聚集”的,之所以這麼稱呼是爲了與InnoDB的聚集索引區分。

InnoDB索引實現

雖然InnoDB也使用B+Tree作爲索引結構,但具體實現方式卻與MyISAM截然不同。

第一個重大區別是InnoDB的數據文件本身就是索引文件。從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。

可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。因爲InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作爲主鍵,如果不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段作爲主鍵,這個字段長度爲6個字節,類型爲長整形。

第二個與MyISAM索引的不同是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作爲data域:

這裏以英文字符的ASCII碼作爲比較準則。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

BTree索引查詢類型

如果我們創建了一個多列的聯合索引,如key(last_name,first_name,birthday),則數據在索引樹中的條目順序就會按照的索引的列順序進行排序。假如last_name相同的兩個條目,會繼續按照first_name進行排序。

BTree的聯合索引順序原理對於建立索引也有一定的指導意義,建立索引時,可以使用BTree索引的查詢類型。BTree索引適用於全鍵值、鍵值範圍或最左前綴查找。

  • 全值匹配:索引中的所有列覆蓋所有的查詢列所進行的匹配。

  • 最左前綴匹配:即按從左往右的優先級進行匹配,如果遇到某列是範圍匹配,之後的列將不再進行匹配。例如,查詢所有的姓爲王,並且名字是強開頭的人,即第一列last_name全匹配,第二列first_name範圍匹配。

  • 匹配列前綴:即只匹配某一列的值得開頭部分。例如可用於查找所有以王開頭的姓的人。這裏只使用了索引的第一列。

  • 匹配範圍值:例如索引可以查找姓爲王年或陳的人。這裏只使用了索引的第一列。

  • 只訪問索引的查詢:即查詢只需要訪問索引,而無需訪問數據行。這也就是我們說的“覆蓋索引”查詢,效率極高。

因爲索引樹種的節點順序是有序的,所以除了按值查找之外,索引還可以用於查詢中的Order By操作。一般來說,如果BTree可以按照某種方式查找到值,那麼也可以按照這種方式用於排序。所以,如果Order By子句滿足前面列出的幾種查詢類型,則這個索引也可以滿足對應的排序需求。

總結

瞭解不同存儲引擎的索引實現方式對於正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實現後,就很容易明白爲什麼不建議使用過長的字段作爲主鍵,因爲所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段作爲主鍵在InnoDB中不是個好主意,因爲InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件爲了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作爲主鍵則是一個很好的選擇。

在下一篇推文中,強哥將向大家介紹Mysql索引有關的高性能策略,瞭解了上面的內容,我們才能更好的去了解Mysql索引創建及優化的原理。

本文參考:

《高性能Mysql第三版》

http://blog.codinglabs.org/articles/theory-of-mysql-index.html

關注公衆號獲取更多內容,有問題也可在公衆號提問哦:

 

強哥叨逼叨

叨逼叨編程、互聯網的見解和新鮮事

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