轉載請註明出處!!!掘金:鳥不拉屎html
本篇文章主要學習了MySQL的索引的數據結構的認識,作一個大概的瞭解便可。node
在關係數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲數據結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。索引的做用至關於圖書的目錄,能夠根據目錄中的頁碼快速查找到所需的內容。mysql
在MySQL中,存儲引擎用相似的方法使用索引,先在索引中找到對應值,而後根據匹配的索引記錄找到對應的行。程序員
首先說明下MySQL的索引主要是基於Hash表或者B+樹。算法
瞭解索引就須要從索引常見的數據結構開始瞭解學習,這裏有集中常見的的索引數據結構。sql
二叉樹是每一個節點最多隻有兩個分支(即不存在分支度大於2的節點)的樹結構。一般被稱之爲「左子樹」和「右子樹」數據庫
左子樹<父節點<=右子樹數組
二叉樹的第i層至多有有2^(i-1)個節點,緩存
深度爲K的二叉樹至多總共有個2^k-1節點(定義根節點所在深度 k0=0),而總計擁有節點數符合的,稱爲「滿二叉樹」;數據結構
二叉樹一般做爲數據結構應用,典型用法是對節點定義一個標記函數,將一些值與每一個節點相關係。這樣標記的二叉樹就能夠實現二叉搜索樹和二叉堆,並應用於高效率的搜索和排序。
同時學習數據結構,這裏還推薦Data Structure Visualizations進行學習,能夠很是直觀的看到數據結構容許的過程,一步一步的怎麼走的均可以很清晰看獲得。
找到其中的Binary Search Trees二叉樹
能夠直觀的看到二叉樹的數據插入過程,以下:
能夠看到二叉樹不適合用做看成索引的,數據量龐大的話,二叉樹的層數會很大,查找效率當然也很慢了。
推薦閱讀:維基百科-二叉樹
是一種自平衡二叉查找樹,典型用途是實現關聯數組。
紅黑樹的結構複雜,但它的操做有着良好的最壞狀況運行時間,而且在實踐中高效:它能夠在O(log n)時間內完成查找,插入和刪除,這裏的n是樹中元素的數目。
紅黑樹遵行如下原則:
下面是一個具體的紅黑樹的圖例:
這些約束確保了紅黑樹的關鍵特性:從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。結果是這個樹大體上是平衡的。由於操做好比插入、刪除和查找某個值的最壞狀況時間都要求與樹的高度成比例,這個在高度上的理論上限容許紅黑樹在最壞狀況下都是高效的,而不一樣於普通的二叉查找樹。
要知道爲何這些性質確保了這個結果,注意到性質4致使了路徑不能有兩個毗連的紅色節點就足夠了。最短的可能路徑都是黑色節點,最長的可能路徑有交替的紅色和黑色節點。由於根據性質5全部最長的路徑都有相同數目的黑色節點,這就代表了沒有路徑能多於任何其餘路徑的兩倍長。
一樣在Data Structure Visualizations中選擇Red-Black Trees紅黑樹進行插入操做能夠直觀的看到紅黑樹的插入過程
一樣紅黑樹也不適用於MySQL的索引,數據量龐大以後,數層也會變大。
推薦閱讀:
因爲沒法裝入內存,則必然依賴磁盤(或SSD)存儲。而內存的讀寫速度是磁盤的成千上萬倍(與具體實現有關),所以,核心問題是「如何減小磁盤讀寫次數」。
首先不考慮頁表機制,假設每次讀、寫都直接穿透到磁盤,那麼:
BST
):讀/寫平均O(log2(n))次;若是樹不平衡,則最差讀/寫O(n)次AVL
):在BST的基礎上加入了自平衡算法,讀/寫最大O(log2(n))次RBT
):另外一種自平衡的查找樹,讀/寫最大O(log2(n))次BST
、AVL
、RBT
很好的將讀寫次數從O(n)優化到O(log2(n));其中,AVL
和RBT
都比BST
多了自平衡的功能,將讀寫次數降到最大O(log2(n))。
假設使用自增主鍵,則主鍵自己是有序的,樹結構的讀寫次數可以優化到樹高,樹高越低讀寫次數越少;自平衡保證了樹結構的穩定。若是想進一步優化,能夠引入B樹
和B+樹
。
又稱:多路平衡查找樹。大多數存儲引擎都支持B樹索引。b樹一般意味着全部的值都是按順序存儲的,而且每個葉子節點到根的距離相同。B樹索引可以加快訪問數據的速度,由於存儲引擎再也不須要進行全表掃描來獲取數據。下圖就是一顆簡單的B樹。
在B樹中,內部(非葉子)節點能夠擁有可變數量的子節點(數量範圍預先定義好)。當數據被插入或從一個節點中移除,它的子節點數量發生變化。爲了維持在預先設定的數量範圍內,內部節點可能會被合併或者分離。
以下圖所示:
只演示了插入的過程,其中能夠經過delete、find執行刪除和查找操做。直觀的感覺到B樹的執行過程。
每一個節點存儲了多個Key和子樹,子樹與Key按順序排列。
同二叉搜索樹相似,每一個節點存儲了多個key和子樹,子樹與key按順序排列。
頁表的目錄是擴展外存+加速磁盤讀寫,一個頁(Page)一般4K(等於磁盤數據塊block的大小,見inode與block的分析),操做系統每次以頁爲單位將內容從磁盤加載到內存(以攤分尋道成本),修改頁後,再擇期將該頁寫回磁盤。考慮到頁表的良好性質,可使每一個節點的大小約等於一個頁(使m很是大),這每次加載的一個頁就能完整覆蓋一個節點,以便選擇下一層子樹;對子樹同理。對於頁表來講,AVL(或RBT)至關於1個key+2個子樹的B樹,因爲邏輯上相鄰的節點,物理上一般不相鄰,所以,讀入一個4k頁,頁面內絕大部分空間都將是無效數據。
假設key、子樹節點指針均佔用4B,則B樹節點最大m * (4 + 4) = 8m B
;頁面大小4KB。則m = 4 * 1024 / 8m = 512
,一個512叉的B樹,1000w的數據,深度最大 log(512/2)(10^7) = 3.02 ~= 4
。對比二叉樹如AVL的深度爲log(2)(10^7) = 23.25 ~= 24
,相差了5倍以上。震驚!B樹索引深度居然如此!
那爲何B數這麼厲害了,還有B+樹的出現呢,必然是解決B樹存在的問題
一、爲定位行數
二、沒法處理範圍查詢
問題1:爲定位行數
數據表的記錄有多個字段,僅僅定位到主鍵是不夠的,還須要定位到數據行。有3個方案解決:
方案1直接pass,存儲數據行將減小頁面中的子樹個數,m減少樹高增大。
方案2的節點中增長了一個字段,假設是4B的指針,則新的m = 4 * 1024 / 12m = 341.33 ~= 341
,深度最大 log(341/2)(10^7) = 3.14 ~= 4
。
方案3的節點m與深度不變,但時間複雜度變爲穩定的O(logm(n))。
方案3能夠考慮。
問題2:沒法處理範圍查詢
實際業務中,範圍查詢的頻率很是高,B樹只能定位到一個索引位置(可能對應多行),很難處理範圍查詢。改動較小的是2個方案:
乍一看感受方案1比方案2好——時間複雜度和常數項都同樣,方案1還不須要改動。可是別忘了局部性原理,無論節點中存儲的是數據行仍是數據行位置,方案2的好處在於,依然能夠利用頁表和緩存預讀下一節點的信息。而方案1則面臨節點邏輯相鄰、物理分離的缺點。 推薦閱讀:
主要變更如上所述:
回顧上一個B樹,一個m階的B樹具備以下幾個特徵:
1.根結點至少有兩個子女。
2.每一箇中間節點都包含k-1個元素和k個孩子,其中 m/2 <= k <= m
3.每個葉子節點都包含k-1個元素,其中 m/2 <= k <= m
4.全部的葉子結點都位於同一層。
5.每一個節點中的元素從小到大排列,節點當中k-1個元素正好是k個孩子包含的元素的值域分劃。
一個m階的B+樹具備以下幾個特徵:
1.有k個子樹的中間節點包含有k個元素(B樹中是k-1個元素),每一個元素不保存數據,只用來索引,全部數據都保存在葉子節點。
2.全部的葉子結點包含了所有元素的信息,及指向含這些元素記錄的指針,且葉子結點自己依關鍵字的大小自小而大順序連接。
3.全部的中間節點元素都同時存在於子節點,在子節點元素中是最大(或最小)元素。
B+樹特性總結
B+樹是B樹的升級版,其有以下特性
一樣在Data Structure Visualizations中選擇B+ TreesB+樹進行插入操做能夠直觀的看到插入過程
在動圖中能夠看出,B+樹的每個葉子節點都有一個指針指向下一個節點,把全部的葉子節點串在一塊兒。索引數據都存儲在葉子節點中。
B+樹相比於B樹,有什麼優點呢:
1.單一節點存儲更多的元素,使得查詢的IO次數更少。
2.全部查詢都要查找到葉子節點,查詢性能穩定。
3.全部葉子節點造成有序鏈表,便於範圍查詢。
總結,B+樹相比B樹的優點有三:1.IO次數更少;2.查詢性能穩定;3.範圍查詢簡便。
推薦閱讀:
hash索引基於hash表實現,Hash 索引是將索引鍵經過 Hash 運算以後,將 Hash運算結果的 Hash 值和所對應的行指針信息存放於一個 Hash 表中。只有精準匹配索引全部列的查詢纔有效。索引的檢索能夠一次定位,不像B-Tree索引須要從根節點出發到目標節點。雖然Hash索引很快,遠高於B-tree索引,可是也有其弊端。
經過navicat工具查看錶設計選項中,從引擎中能夠看到MySQL又這麼多引擎。具體細分到每一個表,不一樣的表引擎能夠不同。
新建一張表t_test_myisam,引擎使用MyISAM,查看原文件能夠看到有3個文件
能夠看到索引和數據是分開的,其中索引文件僅僅保存數據記錄的地址,故屬於非聚簇索引。
MyISAM引擎使用B+Tree做爲索引結構,葉節點的data存放的是數據記錄的地址。以下圖是MyISAM主鍵索引的原理圖。
其中Col1爲主鍵,能夠看出看出MyISAM的索引文件僅保存數據記錄的地址。
在Col2上創建一個輔助索引,以下圖輔助索引原理圖。
能夠看到與主鍵索引沒有任何區別,只不過主鍵索引的key是惟一的,而輔助索引的key能夠重複。
MyISAM中索引檢索的算法爲首先按照B+Tree搜索算法搜索索引,若是指定的Key存在,則取出其data域的值,而後以data域的值爲地址,讀取相應數據記錄。
新建一張表t_test_innodb,引擎使用InnoDB,查看原文件能夠看到有2個文件
InnoDB的索引和數據在一個文件當中。
按照B+Tree組織的一個索引結構。
葉節點保存了完整的數據記錄和索引。這種索引就叫作聚簇索引。
索引的Key是數據的主鍵,所以InnoDB表數據文件自己就是主索引。
以下圖:
能夠看到葉節點包含了完整的數據記錄。
由於InnoDB的數據文件自己要按照主鍵彙集,因此InnoDB要求必須有主鍵。若是沒有顯式指定,則MySQL系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段rowid做爲主鍵,這個字段長度爲6個字節,類型爲長整形。
輔助索引,將途中的第二行name,做爲索引如圖
聚簇索引這種實現方式使得按照主鍵的搜索十分高效,可是首先檢索輔助索引得到主鍵,而後用主鍵到主索引中檢索得到記錄。
因爲InnoDB索引的實現特性,推薦使用整形的自增主鍵。
有三點好處:
m = 4 * 1024 / 54m = 75.85 ~= 76
,深度最大log(76/2)(10^7) = 4.43 ~= 5
,再加上cache缺失、字符串比較的成本,時間成本增長較大。同時,key由4B增加到50B,整棵索引樹的空間佔用增加也是極爲恐怖的(若是二級索引使用主鍵定位數據行,則空間增加更加嚴重)。一是主索引的區別:InnoDB的數據文件自己就是索引文件。而MyISAM的索引和數據是分開的。
二是輔助索引的區別:InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。而MyISAM的輔助索引和主索引沒有多大區別。
InnoDB存儲引擎支持覆蓋索引,即從輔助索引中就能夠獲得查詢的記錄,不須要查詢聚簇索引中的記錄了。能夠減小大量的IO操做。
若是要查詢輔助索引中不含有的字段,得先遍歷輔助索引,再遍歷彙集索引,而若是要查詢的字段值在輔助索引上就有,就不用再查彙集索引了,這顯然會減小IO操做。
兩個或以上的列上的索引。以下圖聯合索引的原理圖:
上圖中的聯合索引有三個,從上到下,嚴格按照排序。
索引能夠簡單如一個列(a),也能夠複雜如多個列(a, b, c, d),即聯合索引
。若是是聯合索引,那麼key也由多個列組成,同時,索引只能用於查找key是否存在(相等),遇到範圍查詢(>、<、between、like左匹配)等就不能進一步匹配了,後續退化爲線性查找。所以,列的排列順序決定了可命中索引的列數。
若有索引(a, b, c, d),查詢條件a = 1 and b = 2 and c > 3 and d = 4
,則會在每一個節點依次命中a、b、c,沒法命中d。也就是最左前綴匹配原則。
不須要考慮=、in等的順序,mysql會自動優化這些條件的順序,以匹配儘量多的索引列。
若有索引(a, b, c, d),查詢條件c > 3 and b = 2 and a = 1 and d < 4
與a = 1 and c > 3 and b = 2 and d < 4
等順序都是能夠的,MySQL會自動優化爲a = 1 and b = 2 and c > 3 and d < 4
,依次命中a、b、c。
有索引列參與計算的查詢條件對索引不友好(甚至沒法使用索引),如from_unixtime(create_time) = '2014-05-29'
。
緣由很簡單,如何在節點中查找到對應key?若是線性掃描,則每次都須要從新計算,成本過高;若是二分查找,則須要針對from_unixtime方法肯定大小關係。
所以,索引列不能參與計算。上述from_unixtime(create_time) = '2014-05-29'
語句應該寫成create_time = unix_timestamp('2014-05-29')
。
若是已有索引(a),想創建索引(a, b),儘可能選擇修改索引(a)爲索引(a, b)。
新建索引的成本很容易理解。而基於索引(a)修改成索引(a, b)的話,MySQL能夠直接在索引a的B+樹上,通過分裂、合併等修改成索引(a, b)。
若是已有索引(a, b),則不須要再創建索引(a),可是若是有必要,則仍然需考慮創建索引(b)。