MySQL數據結構選擇的合理性
從MySQL的角度講,不得不考慮一個現實問題就是磁盤l0。如果我們能讓索引的數據結構盡量減少硬盤的I/O操作,所消耗的時間也就越小。可以說,磁盤的I/0操作次數
對索引的使用效率至關重要。
查找都是索引操作,一般來說索引非常大,尤其是關系型數據庫,當數據量比較大的時候,索引的大小有可能幾個G甚至更多,為了減少索引在內存的占用,數據庫索引是存儲在外部磁盤上的。當我們利用索引查詢的時候,不可能把整個索引全部加載到內存,只能一加載
,那么MySQL衡量查詢效率的標准就是磁盤I0次數。
1、全表遍歷
不用多說
2、Hash結構
Hash 本身是一個函數,又被稱為散列函數,它可以幫助我們大幅提升檢索數據的效率。
Hash算法是通過某種確定性的算法(比如MD5、SHA1、SHA2、SHA3)將輸入轉變為輸出。相同的輸入永遠可以得到相同的輸出
,假設輸入內容有微小偏差,在輸出中通常會有不同的結果。
舉例:如果你想要驗證兩個文件是否相同,那么你不需要把兩份文件直接拿來比對,只需要讓對方把Hash函數計算得到的結果告訴你即可,然后在本地同樣對文件進行Hash函數的運算,最后通過比較這兩個Hash函數的結果是否相同,就可以知道這兩個文件是否相同。
加速查找速度的數據結構,常見的有兩類:
⑴樹,例如平衡二叉搜索樹,查詢/插入/修改/刪除的平均時間復雜度都是O(log2N)
;
⑵哈希,例如HashMap,查詢/插入/修改/刪除的平均時間復雜度都是O(1)
;
采用Hash進行檢索效率非常高,基本上一次檢索就可以找到數據,而B+樹需要自頂向下依次查找,多次訪問節點才能找到數據,中間需要多次l/o操作,從效率來說Hah 比 B+樹更快
。
在哈希的方式下,一個元素k處於h(k)中,即利用哈希函數h,根據關鍵字k計算出槽的位置。函數h將關鍵字域映射到哈希表T[o...m-1]的槽位上。
哈希函數h有可能將兩個不同的關鍵字映射到相同的位置,這叫做碰撞,在數據庫中一般采用鏈接法來解決。在鏈接法中,將散列到同一槽位的元素放在一個鏈表中,Java中的hashmap也是類似操作
Hash結構效率高,那為什么索引結構要設計成樹型呢?
原因1: Hash索引僅能滿足(=)(<>)和IN查詢。如果進行范圍查詢
,哈希型的索引,時間復雜度會退化為o(n);而樹型的“有序”特性,依然能夠保持o(log2N)的高效率。
原因2: Hash 索引還有一個缺陷,數據的存儲是沒有順序
的,在ORDER BY的情況下,使用Hash 索引還需要對數據重新排序。
原因3:對於聯合索引的情況,Hash值是將聯合索引鍵合並后一起來計算的,無法對單獨的一個鍵或者幾個索引鍵進行查詢。
原因4∶對於等值查詢來說,通常Hash 索引的效率更高,不過也存在一種情況,就是索引列的重復值如果很多
,效率就會降低
。這是因為遇到Hash沖突時,需要遍歷桶中的行指針來進行比較,找到查詢的關鍵字,非常耗時。所以,Hash索引通常不會用到重復值多的列上,比如列為性別、年齡的情況等。
Hash索引使用存儲引擎如表所示:
索引/存儲引擎 | MyISAM | InnoDB | Memory |
---|---|---|---|
Hash索引 | 不支持 | 不支持 | 支持 |
Hash索引的適用性:
Hash索引存在着很多限制,相比之下在數據庫中B+樹索引的使用面會更廣,不過也有一些場景采用Hash索引效率更高,比如在鍵值型(key-value)數據庫中,Redis存儲的核心就是Hash表。
MySQL中的Memory存儲引擎支持Hash存儲,如果我們需要用到查詢的臨時表時,就可以選擇Memory存儲引擎,把某個字段設置為Hash索引,比如字符串類型的字段,進行Hash計算之后長度可以縮短到幾個字節。當字段的重復度低,而且經常需要進行等值查詢
的時候,采用Hash索引是個不錯的選擇。
另外,InnoDB本身不支持Hash 索引,但是提供自適應 Hash索引
(Adaptive Hash Index)。什么情況下才會使用自適應Hash索引呢?如果某個數據經常被訪問,當滿足一定條件的時候,就會將這個數據頁的地址存放到Hash表中。這樣下次查詢的時候,就可以直接找到這個頁面的所在位置。這樣讓B+樹也具備了Hash索引的優點。
3、二叉搜索樹
如果我們利用二叉樹作為索引結構,那么磁盤的IO次數和索引樹的高度是相關的。
3.1二叉搜索樹的特點
● 一個節點只能有兩個子節點,也就是一個節點度不能超過2
● 左子節點<本節點;右子節點>=本節點,比我大的向右,比我小的向左
3.2查找規則
我們先來看下最基礎的二叉搜索樹(Binary Search Tree),搜索某個節點和插入節點的規則一樣,我們假設搜索插入的數值為 key:
①如果key大於根節點,則在右子樹中進行查找;
②如果key小於根節點,則在左子樹中進行查找;
③如果key等於根節點,也就是找到了這個節點,返回根節點即可。
但是存在特殊的情況,就是有時候二叉樹的深度非常大。比如我們給出的數據順序是(5,22,23,34,77,89,91)創造出來的二分搜索樹如下圖所示:
上面這棵樹也屬於二分查找樹,但是性能上已經退化成了一條鏈表,查找數據的時間復雜度變成了0(n)。你能看出來這個樹的深度是7,最多需要7次比較才能找到節點。
為了提高查詢效率,就需要減少磁盤IO數
。為了減少磁盤lo的次數,就需要盡量降低樹的高度
,需要把原來“瘦高”的樹結構變的“矮胖”,樹的每層的分叉越多越好。
4、AVL樹(平衡二叉搜索樹)
為了解決上面二叉查找樹退化成鏈表的問題,人們提出了平衡二叉搜索樹(Balanced Binary Tree)
,又稱為AVL樹(有別於AVL算法),它在二叉搜索樹的基礎上增加了約束,具有以下性質:
它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。
這里說一下,常見的平衡二叉樹有很多種,包括了平衡二叉搜索樹、紅黑樹、數堆、伸展樹
。平衡二叉搜索樹是最早提出來的自平衡二叉搜索樹,當我們提到平衡二叉樹時一般指的就是平衡二叉搜索樹。事實上,第一棵樹就屬於平衡二叉搜索樹,搜索時間復雜度就是o( log2n)
。
數據查詢的時間主要依賴於磁盤V/o的次數,如果我們采用二叉樹的形式,即使通過平衡二叉搜索樹進行了改進,樹的深度也是o(log2n),當n 比較大時,深度也是比較高的,比如下圖的情況:
每訪問一次節點就需要進行一次磁盤I/0 操作
,對於上面的樹來說,我們需要進行5次I/O操作。雖然平衡二叉樹的效率高,但是樹的深度也同樣高,這就意味着磁盤I/O操作次數多,會影響整體數據查詢的效率。
針對同樣的數據,如果我們把二叉樹改成M叉樹
(M>2)呢?當M=3時,同樣的31個節點可以由下面的三叉樹來進行存儲:
你能看到此時樹的高度降低了,當數據量N大的時候,以及樹的分叉數M大的時候,M叉樹的高度會遠小於二叉樹的高度(M>2)。所以,我們需要把樹從“瘦高"變“矮胖”
。
5、B-Tree
B樹的英文是Balance Tree,也就是多路平衡查找樹
。簡寫為B-Tree (注意橫杠表示這兩個單詞連起來的意思,不是減號)。它的高度遠小於平衡二叉樹的高度。
B樹的結構如下圖:
B樹作為多路平衡查找樹,它的每一個節點最多可以包括M個子節點,M稱為B樹的階
。每個磁盤塊中包括了關鍵字
和子節點的指針
。如果一個磁盤塊中包括了×個關鍵字,那么指針數就是x+1。對於一個10o階的B樹來說,如果有3層的話最多可以存儲約100萬的索引數據。對於大量的索引數據來說,采用B樹的結構是非常適合的,因為樹的高度要遠小於二叉樹的高度。
小結:
① B樹在插入和刪除節點的時候如果導致樹不平衡,就通過自動調整節點的位置來保持樹的自平衡。
② 關鍵字集合分布在整棵樹中,即葉子節點和非葉子節點都存放數據。搜索有可能在非葉子節點結束。
③ 其搜索性能等價於在關鍵字全集內做一次二分查找。
6、B+Tree
B+樹也是一種多路搜索樹,基於B樹做出了改進
,主流的DBMS都支持B+樹的索引方式,比如 MysQL。相比於B-Tree,B+Tree適合文件索引系統
。
● MySQL官網說明:
B+樹和B樹的差異在於以下幾點:
① 有k個孩子的節點就有k個關鍵字。也就是孩子數量=關鍵字數,而B樹中,孩子數量=關鍵字數+1。
② 非葉子節點的關鍵字也會同時存在在子節點中,並且是在子節點中所有關鍵字的最大(或最小)。
③ 非葉子節點僅用於索引,不保存數據記錄,跟記錄有關的信息都放在葉子節點中。而B樹中,非葉子節點既 保存索引,也保存數據記錄
。
④ 所有關鍵字都在葉子節點出現,葉子節點構成一個有序鏈表,而且葉子節點本身按照關鍵字的大小從小到大
順序鏈接。
思考:為了減少IO,索引樹會一次性加載嗎?
1、數據庫索引是存儲在磁盤上的,如果數據量很大,必然導致索引的大小也會很大,超過幾個G。
2、當我們利用索引查詢的時候,是不可能獎全部幾個G的索引都加載進內存的,我們能做的只是:逐一加載每一個磁盤頁,因為磁盤頁對應着索引樹的節點。
思考:B+樹的存儲能力如何?為何說一般查找行記錄,最多只需1~3次的磁盤IO
InnoDB存儲引擎中頁的大小為16KB,一般表的主鍵類型為INT(占用4個字節)或BIGINT(占用8個字節),指針類型也一般為4或8個字節,也就是說一個頁(B+Tree中的一個節點)中大概存儲16KB/(8B+8B)=1K個鍵值(因為是估值,為方便計算,這里的K取值為103。也就是說一個深度為3的B+Tree索引可以維護103*103*1043=10億條記錄。(這里假定一個數據頁也存儲103條行記錄數據了)
實際情況中每個節點可能不能填充滿,因此在數據庫中,
B+Tree 的高度一般都在2~4層
。MySQL的InnoDB存儲引擎在設計時是將根節點常駐內存的,也就是說查找某一鍵值的行記錄時最多只需要1~3次磁盤I/0操作。
思考:為什么說B+樹比B-樹更適合實際應用中操作系統的文件索引和數據庫索引?
1、B+樹的磁盤讀寫代價更低
B+樹的內部結點並沒有指向關鍵字具體信息的指針。因此其內部結點相對B樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那么盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的需要查找的關鍵字也就越多。相對來說Io讀寫次數也就降低了。
2、B+樹的查詢效率更加穩定
由於非終結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。
思考:Hash索引和B+樹索引的區別
我們之前講到過B+樹索引的結構,Hash索引結構和B+樹的不同,因此在索引使用上也會有差別。
1、Hash 索引不能進行范圍查詢
,而B+樹可以。這是因為Hash索引指向的數據是無序的,而B+樹的葉子節點是個有序的鏈表。
2、Hash索引不支持聯合索引的最左側原則
(即聯合索引的部分索引無法使用),而B+樹可以。對於聯合索引來說,Hash索引在計算Hash值的時候是將索引鍵合並后再一起計算Hash值,所以不會針對每個索引單獨計算Hash值。因此如果用到聯合索引的一個或者幾個索引時,聯合索引無法被利用。
3、Hash索引不支持ORDER BY排序
,因為Hash索引指向的數據是無序的,因此無法起到排序優化的作用,而B+樹索引數據是有序的,可以起到對該字段ORDER BY排序優化的作用。同理,我們也無法用Hash索引進行模糊查詢
,而B+樹使用LIKE進行模糊查詢的時候,LIKE后面后模糊查詢(比如%結尾)的話就可以起到優化作用4、
InnoDB不支持哈希索引
7、R樹
R-Tree在MySQL很少使用,僅支持geometry數據類型
,支持該類型的存儲引擎只有myisam、bdb、innodb、ndb、archive幾種。舉個R樹在現實領域中能夠解決的例子:查找2o英里以內所有的餐廳。如果沒有R樹你會怎么解決?一般情況下我們會把餐廳的坐標(xy)分為兩個字段存放在數據庫中,一個字段記錄經度,另一個字段記錄緯度。這樣的話我們就需要遍歷所有的餐廳獲取其位置信息,然后計算是否滿足要求。如果一個地區有100家餐廳的話,我們就要進行10o次位置計算操作了,如果應用到谷歌、百度地圖這種超大數據庫中,這種方法便必定不可行了。R樹就很好的解決了這種高維空間搜索問題
。它把B樹的思想很好的擴展到了多維空間,采用了B樹分割空間的思想,並在添加、刪除操作時采用合並、分解結點的方法,保證樹的平衡性。因此,R樹就是一棵用來存儲高維數據的平衡樹
。相對於B-Tree,R-Tree的優勢在於范圍查找。
索引/存儲引擎 | MyISAM | iNNOdb | Memory |
---|---|---|---|
R-Tree索引 | 支持 | 支持 | 不支持 |
8、小結
使用索引可以幫助我們從海量的數據中快速定位想要查找的數據,不過索引也存在一些不足,比如占用存儲空間、降低數據庫寫操作的性能等,如果有多個索引還會增加索引選擇的時間。當我們使用索引時,需要平衡索引的利(提升查詢效率)和弊(維護索引所需的代價)。
在實際工作中,我們還需要基於需求和數據本身的分布情況來確定是否使用索引,盡管索引不是萬能的
,但數據量大的時候不使用索引是不可想象的
,畢竟索引的本質,是幫助我們提升數據檢索的效率。