MySQL的InnoDB索引原理詳解

MySQL的InnoDB索引原理詳解

http://www.admin10000.com/document/5372.htmlhtml

摘要:

  本篇介紹下Mysql的InnoDB索引相關知識,從各類樹到索引原理到存儲的細節。mysql

  InnoDB是Mysql的默認存儲引擎(Mysql5.5.5以前是MyISAM,文檔)。本着高效學習的目的,本篇以介紹InnoDB爲主,少許涉及MyISAM做爲對比。算法

  這篇文章是我在學習過程當中總結完成的,內容主要來自書本和博客(參考文獻會給出),過程當中加入了一些本身的理解,描述不許確的地方煩請指出。sql

  1 各類樹形結構

  原本不打算從二叉搜索樹開始,由於網上已經有太多相關文章,可是考慮到清晰的圖示對理解問題有很大幫助,也爲了保證文章完整性,最後仍是加上了這部分。數據庫

  先看看幾種樹形結構:數據結構

  1 搜索二叉樹:每一個節點有兩個子節點,數據量的增大必然致使高度的快速增長,顯然這個不適合做爲大量數據存儲的基礎結構。性能

  2 B樹:一棵m階B樹是一棵平衡的m路搜索樹。最重要的性質是每一個非根節點所包含的關鍵字個數 j 知足:┌m/2┐ - 1 <= j <= m - 1;一個節點的子節點數量會比關鍵字個數多1,這樣關鍵字就變成了子節點的分割標誌。通常會在圖示中把關鍵字畫到子節點中間,很是形象,也容易和後面的B+樹區分。因爲數據同時存在於葉子節點和非葉子結點中,沒法簡單完成按順序遍歷B樹中的關鍵字,必須用中序遍歷的方法。學習

  3 B+樹:一棵m階B樹是一棵平衡的m路搜索樹。最重要的性質是每一個非根節點所包含的關鍵字個數 j 知足:┌m/2┐ - 1 <= j <= m;子樹的個數最多能夠與關鍵字同樣多。非葉節點存儲的是子樹裏最小的關鍵字。同時數據節點只存在於葉子節點中,且葉子節點間增長了橫向的指針,這樣順序遍歷全部數據將變得很是容易。測試

  4 B*樹:一棵m階B樹是一棵平衡的m路搜索樹。最重要的兩個性質是1每一個非根節點所包含的關鍵字個數 j 知足:┌m2/3┐ - 1 <= j <= m;2非葉節點間添加了橫向指針。優化

  B/B+/B*三種樹有類似的操做,好比檢索/插入/刪除節點。這裏只重點關注插入節點的狀況,且只分析他們在當前節點已滿狀況下的插入操做,由於這個動做稍微複雜且能充分體現幾種樹的差別。與之對比的是檢索節點比較容易實現,而刪除節點只要完成與插入相反的過程便可(在實際應用中刪除並非插入的徹底逆操做,每每只刪除數據而保留下空間爲後續使用)。

  先看B樹的分裂,下圖的紅色值即爲每次新插入的節點。每當一個節點滿後,就須要發生分裂(分裂是一個遞歸過程,參考下面7的插入致使了兩層分裂),因爲B樹的非葉子節點一樣保存了鍵值,因此已滿節點分裂後的值將分佈在三個地方:1原節點,2原節點的父節點,3原節點的新建兄弟節點(參考5,7的插入過程)。分裂有可能致使樹的高度增長(參考3,7的插入過程),也可能不影響樹的高度(參考5,6的插入過程)。

  B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的數據複製到新結點,最後在父結點中增長新結點的指針;B+樹的分裂隻影響原結點和父結點,而不會影響兄弟結點,因此它不須要指向兄弟節點的指針。

  B*樹的分裂:當一個結點滿時,若是它的下一個兄弟結點未滿,那麼將一部分數據移到兄弟結點中,再在原結點插入關鍵字,最後修改父結點中兄弟結點的關鍵字(由於兄弟結點的關鍵字範圍改變了)。若是兄弟也滿了,則在原結點與兄弟結點之間增長新結點,並各複製1/3的數據到新結點,最後在父結點增長新結點的指針。能夠看到B*樹的分裂很是巧妙,由於B*樹要保證分裂後的節點還要2/3滿,若是採用B+樹的方法,只是簡單的將已滿的節點一分爲二,會致使每一個節點只有1/2滿,這不知足B*樹的要求了。因此B*樹採起的策略是在本節點滿後,繼續插入兄弟節點(這也是爲何B*樹須要在非葉子節點加一個兄弟間的鏈表),直到把兄弟節點也塞滿,而後拉上兄弟節點一塊兒湊份子,本身和兄弟節點各出資1/3成立新節點,這樣的結果是3個節點恰好是2/3滿,達到B*樹的要求,皆大歡喜。

  B+樹適合做爲數據庫的基礎結構,徹底是由於計算機的內存-機械硬盤兩層存儲結構。內存能夠完成快速的隨機訪問(隨機訪問即給出任意一個地址,要求返回這個地址存儲的數據)可是容量較小。而硬盤的隨機訪問要通過機械動做(1磁頭移動 2盤片轉動),訪問效率比內存低幾個數量級,可是硬盤容量較大。典型的數據庫容量大大超過可用內存大小,這就決定了在B+樹中檢索一條數據極可能要藉助幾回磁盤IO操做來完成。以下圖所示:一般向下讀取一個節點的動做可能會是一次磁盤IO操做,不過非葉節點一般會在初始階段載入內存以加快訪問速度。同時爲提升在節點間橫向遍歷速度,真實數據庫中可能會將圖中藍色的CPU計算/內存讀取優化成二叉搜索樹(InnoDB中的page directory機制)。

  真實數據庫中的B+樹應該是很是扁平的,能夠經過向表中順序插入足夠數據的方式來驗證InnoDB中的B+樹到底有多扁平。咱們經過以下圖的CREATE語句創建一個只有簡單字段的測試表,而後不斷添加數據來填充這個表。經過下圖的統計數據(來源見參考文獻1)能夠分析出幾個直觀的結論,這幾個結論宏觀的展示了數據庫裏B+樹的尺度。

  1 每一個葉子節點存儲了468行數據,每一個非葉子節點存儲了大約1200個鍵值,這是一棵平衡的1200路搜索樹!

  2 對於一個22.1G容量的表,也只須要高度爲3的B+樹就能存儲了,這個容量大概能知足不少應用的須要了。若是把高度增大到4,則B+樹的存儲容量馬上增大到25.9T之巨!

  3 對於一個22.1G容量的表,B+樹的高度是3,若是要把非葉節點所有加載到內存也只須要少於18.8M的內存(如何得出的這個結論?由於對於高度爲2的樹,1203個葉子節點也只須要18.8M空間,而22.1G從良表的高度是3,非葉節點1204個。同時咱們假設葉子節點的尺寸是大於非葉節點的,由於葉子節點存儲了行數據而非葉節點只有鍵和少許數據。),只使用如此少的內存就能夠保證只須要一次磁盤IO操做就檢索出所需的數據,效率是很是之高的。

  2 Mysql的存儲引擎和索引

  能夠說數據庫必須有索引,沒有索引則檢索過程變成了順序查找,O(n)的時間複雜度幾乎是不能忍受的。咱們很是容易想象出一個只有單關鍵字組成的表如何使用B+樹進行索引,只要將關鍵字存儲到樹的節點便可。當數據庫一條記錄裏包含多個字段時,一棵B+樹就只能存儲主鍵,若是檢索的是非主鍵字段,則主鍵索引失去做用,又變成順序查找了。這時應該在第二個要檢索的列上創建第二套索引。  這個索引由獨立的B+樹來組織。有兩種常見的方法能夠解決多個B+樹訪問同一套表數據的問題,一種叫作聚簇索引(clustered index ),一種叫作非聚簇索引(secondary index)。這兩個名字雖然都叫作索引,但這並非一種單獨的索引類型,而是一種數據存儲方式。對於聚簇索引存儲來講,行數據和主鍵B+樹存儲在一塊兒,輔助鍵B+樹只存儲輔助鍵和主鍵,主鍵和非主鍵B+樹幾乎是兩種類型的樹。對於非聚簇索引存儲來講,主鍵B+樹在葉子節點存儲指向真正數據行的指針,而非主鍵。

  InnoDB使用的是聚簇索引,將主鍵組織到一棵B+樹中,而行數據就儲存在葉子節點上,若使用"where id = 14"這樣的條件查找主鍵,則按照B+樹的檢索算法便可查找到對應的葉節點,以後得到行數據。若對Name列進行條件搜索,則須要兩個步驟:第一步在輔助索引B+樹中檢索Name,到達其葉子節點獲取對應的主鍵。第二步使用主鍵在主索引B+樹種再執行一次B+樹檢索操做,最終到達葉子節點便可獲取整行數據。

  MyISM使用的是非聚簇索引,非聚簇索引的兩棵B+樹看上去沒什麼不一樣,節點的結構徹底一致只是存儲的內容不一樣而已,主鍵索引B+樹的節點存儲了主鍵,輔助鍵索引B+樹存儲了輔助鍵。表數據存儲在獨立的地方,這兩顆B+樹的葉子節點都使用一個地址指向真正的表數據,對於表數據來講,這兩個鍵沒有任何差異。因爲索引樹是獨立的,經過輔助鍵檢索無需訪問主鍵的索引樹。

  爲了更形象說明這兩種索引的區別,咱們假想一個表以下圖存儲了4行數據。其中Id做爲主索引,Name做爲輔助索引。圖示清晰的顯示了聚簇索引和非聚簇索引的差別。

  咱們重點關注聚簇索引,看上去聚簇索引的效率明顯要低於非聚簇索引,由於每次使用輔助索引檢索都要通過兩次B+樹查找,這不是畫蛇添足嗎?聚簇索引的優點在哪?

  1 因爲行數據和葉子節點存儲在一塊兒,這樣主鍵和行數據是一塊兒被載入內存的,找到葉子節點就能夠馬上將行數據返回了,若是按照主鍵Id來組織數據,得到數據更快。

  2 輔助索引使用主鍵做爲"指針" 而不是使用地址值做爲指針的好處是,減小了當出現行移動或者數據頁分裂時輔助索引的維護工做,使用主鍵值看成指針會讓輔助索引佔用更多的空間,換來的好處是InnoDB在移動行時無須更新輔助索引中的這個"指針"。也就是說行的位置(實現中經過16K的Page來定位,後面會涉及)會隨着數據庫裏數據的修改而發生變化(前面的B+樹節點分裂以及Page的分裂),使用聚簇索引就能夠保證無論這個主鍵B+樹的節點如何變化,輔助索引樹都不受影響。

  3 Page結構

  若是說前面的內容偏向於解釋原理,那後面就開始涉及具體實現了。

  理解InnoDB的實現不得不提Page結構,Page是整個InnoDB存儲的最基本構件,也是InnoDB磁盤管理的最小單位,與數據庫相關的全部內容都存儲在這種Page結構裏。Page分爲幾種類型,常見的頁類型有數據頁(B-tree Node)Undo頁(Undo Log Page)系統頁(System Page) 事務數據頁(Transaction System Page)等。單個Page的大小是16K(編譯宏UNIV_PAGE_SIZE控制),每一個Page使用一個32位的int值來惟一標識,這也正好對應InnoDB最大64TB的存儲容量(16Kib * 2^32 = 64Tib)。一個Page的基本結構以下圖所示:

  每一個Page都有通用的頭和尾,可是中部的內容根據Page的類型不一樣而發生變化。Page的頭部裏有咱們關心的一些數據,下圖把Page的頭部詳細信息顯示出來:

 

  咱們重點關注和數據組織結構相關的字段:Page的頭部保存了兩個指針,分別指向前一個Page和後一個Page,頭部還有Page的類型信息和用來惟一標識Page的編號。根據這兩個指針咱們很容易想象出Page連接起來就是一個雙向鏈表的結構。

  再看看Page的主體內容,咱們主要關注行數據和索引的存儲,他們都位於Page的User Records部分,User Records佔據Page的大部分空間,User Records由一條一條的Record組成,每條記錄表明索引樹上的一個節點(非葉子節點和葉子節點)。在一個Page內部,單鏈表的頭尾由固定內容的兩條記錄來表示,字符串形式的"Infimum"表明開頭,"Supremum"表明結尾。這兩個用來表明開頭結尾的Record存儲在System Records的段裏,這個System Records和User Records是兩個平行的段。InnoDB存在4種不一樣的Record,它們分別是1主鍵索引樹非葉節點 2主鍵索引樹葉子節點 3輔助鍵索引樹非葉節點 4輔助鍵索引樹葉子節點。這4種節點的Record格式有一些差別,可是它們都存儲着Next指針指向下一個Record。後續咱們會詳細介紹這4種節點,如今只須要把Record當成一個存儲了數據同時含有Next指針的單鏈表節點便可。

  User Record在Page內以單鏈表的形式存在,最初數據是按照插入的前後順序排列的,可是隨着新數據的插入和舊數據的刪除,數據物理順序會變得混亂,但他們依然保持着邏輯上的前後順序。

  把User Record的組織形式和若干Page組合起來,就看到了稍微完整的形式。

  如今看下如何定位一個Record:

  1 經過根節點開始遍歷一個索引的B+樹,經過各層非葉子節點最終到達一個Page,這個Page裏存放的都是葉子節點。

  2 在Page內從"Infimum"節點開始遍歷單鏈表(這種遍歷每每會被優化),若是找到該鍵則成功返回。若是記錄到達了"supremum",說明當前Page裏沒有合適的鍵,這時要藉助Page的Next Page指針,跳轉到下一個Page繼續從"Infimum"開始逐個查找。

  詳細看下不一樣類型的Record裏到底存儲了什麼數據,根據B+樹節點的不一樣,User Record能夠被分紅四種格式,下圖種按照顏色予以區分。

  1 主索引樹非葉節點(綠色)

  1 子節點存儲的主鍵裏最小的值(Min Cluster Key on Child),這是B+樹必須的,做用是在一個Page裏定位到具體的記錄的位置。

  2 最小的值所在的Page的編號(Child Page Number),做用是定位Record。

  2 主索引樹葉子節點(黃色)

  1 主鍵(Cluster Key Fields),B+樹必須的,也是數據行的一部分

  2 除去主鍵之外的全部列(Non-Key Fields),這是數據行的除去主鍵的其餘全部列的集合。

  這裏的1和2兩部分加起來就是一個完整的數據行。

  3 輔助索引樹非葉節點非(藍色)

  1 子節點裏存儲的輔助鍵值裏的最小的值(Min Secondary-Key on Child),這是B+樹必須的,做用是在一個Page裏定位到具體的記錄的位置。

  2 主鍵值(Cluster Key Fields),非葉子節點爲何要存儲主鍵呢?由於輔助索引是能夠不惟一的,可是B+樹要求鍵的值必須惟一,因此這裏把輔助鍵的值和主鍵的值合併起來做爲在B+樹中的真正鍵值,保證了惟一性。可是這也致使在輔助索引B+樹中非葉節點反而比葉子節點多了4個字節。(即下圖中藍色節點反而比紅色多了4字節)

  3 最小的值所在的Page的編號(Child Page Number),做用是定位Record。

  4 輔助索引樹葉子節點(紅色)

  1 輔助索引鍵值(Secondary Key Fields),這是B+樹必須的。

  2 主鍵值(Cluster Key Fields),用來在主索引樹裏再作一次B+樹檢索來找到整條記錄。

  下面是本篇最重要的部分了,結合B+樹的結構和前面介紹的4種Record的內容,咱們終於能夠畫出一幅全景圖。因爲輔助索引的B+樹與主鍵索引有類似的結構,這裏只畫出了主鍵索引樹的結構圖,只包含了"主鍵非葉節點"和"主鍵葉子節點"兩種節點,也就是上圖的的綠色和黃色的部分。

  把上圖還原成下面這個更簡潔的樹形示意圖,這就是B+樹的一部分。注意Page和B+樹節點之間並無一一對應的關係,Page只是做爲一個Record的保存容器,它存在的目的是便於對磁盤空間進行批量管理,上圖中的編號爲47的Page在樹形結構上就被拆分紅了兩個獨立節點。

  至此本篇就算結束了,本篇只是對InnoDB索引相關的數據結構和實現進行了一些梳理總結,並未涉及到Mysql的實戰經驗。這主要是基於幾點緣由:

  1 原理是基石,只有充分了解InnoDB索引的工做方式,咱們纔有能力高效的使用好它。

  2 原理性知識特別適合使用圖示,我我的很是喜歡這種表達方式。

  3 關於InnoDB優化,在《高性能Mysql》裏有更加全面的介紹,對優化Mysql感興趣的同窗徹底能夠本身獲取相關知識,我本身的積累還未達到能分享這些內容的地步。

  另:對InnoDB實現有更多興趣的同窗能夠看看Jeremy Cole的博客(參考文獻三篇文章的來源),這位老兄曾前後在Mysql,Yahoo,Twitter,Google從事數據庫相關工做,他的文章很是棒!

分類: 數據庫

相關文章
相關標籤/搜索