不同數據庫存儲引擎技術的優劣勢分析
?1. 什么是數據庫的存儲引擎技術
數據庫的存儲引擎是什么?它主要解決什么問題?
很多數據庫管理員可能對存儲引擎并不熟悉,因為大多數常見關系型數據庫基本只有一種存儲引擎,沒有給我們選擇和設計的機會,例如Oracle、SQL Server。但是如果我們接觸MySQL以及其他一些NoSQL分布式數據庫比較多的人可能對存儲引擎就會深有感受。首先,我們認為存儲引擎就是為了實現數據存儲以及數據檢索而實現的解決方案,如何建立索引,如果實現更新,如何檢索數據等都是它的功能實現范疇。常見的存儲引擎有哈希存儲引擎和樹存儲引擎,樹存儲引擎又分為B-Tree、B+Tree、LSM-Tree等若干種。不同的存儲引擎對數據的結構、數據的存儲方式、數據的讀取方式等都有不同的要求和特點。
2. 不同存儲引擎如何建立索引
2.1 B-Tree
B樹數據結構其實是在我們大學當中所學數據結構課程當中的二叉樹基礎上的一種升級和改進。最早是由德國計算機科學家Rudolf Bayer等人于1972年在論文 《Organization and Maintenance of Large Ordered Indexes》提出。

如圖所示,B樹事實上是一種平衡的多叉查找樹,也就是說最多可以開m個叉(m>=2),我們稱之為m階b樹。總的來說,m階B樹滿足以下條件:
(1)每個節點至多可以擁有m棵子樹。
(2)根節點,只有至少有2個節點(極端情況,就是一棵樹就一個根節點)。
(3)非根非葉的節點至少有Ceil(m/2)個子樹( 圖中5階B樹,每個節點至少有3個子樹)。
(4)非葉節點中信息包括[n,A0,K1,…,Kn,An],其中n表示該節點保存的關鍵字個數,K為關鍵字且Ki(對應到關系型數據庫當中的信息,就是二位表當中記錄的主鍵信息)。
(5)從根到葉子的每一條路徑都有相同的長度,也就是指向這些節點的指針為空。
2.2 B+Tree
B+樹實際上是B-Tree的升級版,它是基于原有數據結構的不足支持進行系列改造之后形成的存儲引擎技術,如圖所示:

從圖中所示的狀況我們可以很直觀感受到:B+樹與 B樹最大的不同是所有數據記錄都保存在葉子節點中,葉子結點是有指針將所有數據連接起來的。具體來說,B+樹與B樹的主要區別:
(1) 有n棵子樹的節點含有n個關鍵字(也有認為是n-1個關鍵字);
(2) 所有的葉子節點包含了全部的關鍵字,及指向含這些關鍵字記錄的指針,且葉子節點本身根據關鍵字自小而大順序連接;
(3) 非葉子節點可以看成索引部分,節點中僅含有其子樹中的最大或最小關鍵字.
由于采用了這樣的結構,B+樹對比B樹有以下兩方面優點:
首先,索引節點上由于只有索引而沒有數據,所以索引節點上能存儲比B樹更多的索引,這樣樹的高度就會更矮。那么查詢的時間復雜度就會更低。再有,因為數據都集中在葉子節點了并且葉子節點增加前后指針,指向同一個父節點的相鄰兄弟節點,給范圍查詢提供遍歷。而如果使用B樹結構,由于數據既可以存儲在內部節點也可以存儲在葉子節點,范圍查詢是很繁瑣的。
2.3 Hash
哈希存儲引擎的數據庫本身只是一個健值存儲系統,數據庫當中存儲的數據以文件的物理形式表現,但是每一個物理文件當中存儲的具體數據內容主要包含兩種:一種是主健,另外一種是具體的數據值。用戶通過put(key,value)來寫入數據,或者通過get(key)接口來獲取數據,每條記錄都是一個健值對。
哈希索引本身有很多種實現方式,有基于靜態哈希實現的索引結構,也有基于動態哈希實現的索引結構,其具體的實現方式依賴于不同的數據庫。一般來講,哈希索引表的結構如圖所示:
PK1→ | File-ID1 | Value-Lenth1 | Value-Position1 | Time-Stamp1 |
PK2→ | File-ID2 | Value-Lenth2 | Value-Position2 | Time-Stamp2 |
PK3→ | File-ID3 | Value-Lenth3 | Value-Position3 | Time-Stamp3 |
PKn→ | File-ID4 | Value-Lenth4 | Value-Position4 | Time-Stamp4 |
我們知道HashMap< K,V>,可以通過K 來獲取V, 對于以上的哈希索引來說,PrimaryKey就是我們要取得的V值。比如 (PK = key mod 2) 叫做散列函數或者哈希函數,那么PK的區間范圍,我們稱其為散列地址。存儲的時候 通過散列函數算出散列地址,然后把value的值存入,查找的時候 通過散列函數算出散列地址 ,然后讀出數據。
3. 不同存儲引擎的數據檢索
3.1 B-Tree & B+Tree
對于基于二叉樹數據結構基礎之上形成的樹的存儲結構,那么其查詢數據最核心的算法就是二分法查找算法,即通過鍵值的比較排除一定比例的可能性,從而縮小數據查找的范圍,最終通過幾次比較定位到要查找的數據。直觀表現期間,我們還是以圖為例:

按照圖示,我們查找的數據是L,首先,將根節點的數據塊從磁盤讀入內存,讀出P值,比較發現小于P;
接著,遍歷根節點左指針指向數據塊,讀出C、F、J、M值,順序比較后,找到J&M之間的指針;
最后,遍歷指針指向數據塊,讀出K、L值,定位所查詢的數據L。
根據以上算法,那么本次查找經歷了三次磁盤的讀取,三次內存數據的比較。由此看見,B樹檢索的時間復雜度主要取決于樹的深度以及節點內保存的數據數量的多少。
對于B+樹的檢索,其實算法與B樹非常類似,其主要區別在于B+樹的所有檢索操作都不會在非葉子節點結束,每一個檢索都會經歷相同的長度,那就是從根節點到葉子節點,途中經歷的非葉子節點只保留索引信息,只有葉子節點才會保留所有鍵值數據。這種算法把所有遍歷的復雜度留在了葉子節點的掃描上,減少了檢索途中的IO次數,保證了葉子節點掃描的局部優勢,同時也保障了所有檢索操作的時間復雜度相對的穩定性。因此大部分關系型數據庫選擇的是B+樹作為其存儲引擎。
3.2 Hash
對于Hash存儲引擎的數據檢索,我們首先要聊到它的增、刪、改操作。
數據文件分活躍狀態和陳舊狀態兩種。
數據的增加操作,用戶寫入的記錄直接追加到活動文件,因此活動文件會越來越大,當到達一定大小時,活躍的數據文件會被凍結。引擎重新建立一個活躍文件用于寫入,而之前的活躍文件則變為陳舊的數據文件。寫入記錄的同時還要在索引哈希表中添加索引記錄。
數據的刪除操作,用戶不直接刪除記錄,而是新增一條相同Key的記錄,把Value值設置一個刪除的標記。原有記錄依然存在于數據文件中,然后更新索引哈希表。這樣的話,在處理檢索操作的時候,用戶就會最先讀到哈希索引表里面的空值記錄,原有記錄后續處理。
數據的更新操作,不支持隨機寫入。對于存儲系統的基本功能中更新,實際上和增加數據操作都是一樣的,都是直接寫入活動數據文件。同時修改索引哈希表中對應記錄的值。這個時候,實際上數據文件中同一個Key值對應了多條記錄,根據時間戳記錄來判斷,以最新的數據為準。
數據的讀取操作,讀取時,首先從索引哈希表中定位到記錄在數據文件中的具體位置,然后通過讀取出對應的記錄。當然在讀取索引表的時候,索引的結構有可能是索引樹結構,在檢索索引的過程當中會有一定的復雜度,具體根據樹的深度來判斷檢索的復雜度。
4. 不同存儲引擎技術的選擇設計
4.1 B&B+樹的優劣勢分析
首先,通過對B樹和B+樹的檢索算法特點來看,從使用的角度上來說,B樹索引存儲結構多用于OLTP型的數據庫,因為這類數據庫主要以事務,或是行級別的讀取和存儲為主的(比如MYSQL)。換言之,這種類型的數據庫更多的操作是小批量或單行級別的隨機讀取和更新,并且還有事務方面的需求。在此前提條件之下,之所以有B+樹的誕生,取決于以下兩點:a. 磁盤讀寫代價更低。B樹的內部結點并無指向關鍵字具體信息的指針。所以其內部結點相對B樹更小。若是把全部同一內部結點的關鍵字存放在同一盤塊中,那么盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的須要查找的關鍵字也就越多。相對來講IO讀寫次數也就下降了。b. B+樹只要遍歷葉子節點就能夠實現整棵樹的遍歷。并且在數據庫中基于范圍的查詢是很是頻繁的,而B樹實現這樣的操作需要的代價非常高,效率非常低。
其次,通過對B樹和B+樹的更新和刪除算法特點來看,雖然算法相對哈希及其他存儲引擎實現的算法來講會顯得非常復雜,代價很高。但是從另外一個側面來看,正是由于它沒有通過大量的數據追加實現更新和刪除,它就無需去管理那些不同時間戳版本的重復數據。有效地利用了磁盤空間和內存空間,這個也與我們OLTP型關系型數據庫的規模以及通用服務器硬件的配置非常匹配。
4.2 Hash的優劣勢分析
首先,從數據結構特點來看。我們在前邊提到了它的數據結構以及索引表的結構,我們發現最大的特點就是在于所有的這些數據結構都是以模式為基礎的。所以基于這一點來看,它本身更適合能以鍵值對的模式表示的數據存儲,無論是固定的鍵值,還是變動的鍵值。其次,我們來分析哈希存儲引擎索引表檢索算法的特點。如果沖突處理的算法的當,它大概率可以通過一次哈希函數就可以定位到數據的基本位置,與B-Tree存儲引擎相比較而言,它少了樹根、樹枝、樹葉節點的遍歷和多次的讀取操作。從哈希存儲引擎添加、刪除、更新數據的算法特點來看,基于除了檢索之外所有的數據操作都是通過添加新數據來變相實現。同樣與B-Tree存儲引擎相比較而言,添加一條新的紀錄遠比檢索、加鎖、修改、放鎖這個過程要效率很多。從這個意義上來講,如果我們能把這些符合鍵值對要求的索引表數據全部引入到內存,那么對于隨機讀取的并發能力提升無疑是巨大的質變,這也是它能被Redis、Memcache這類內存數據庫選中的重要原因。最后,所以對于事務性要求不是非常強,并且包含大量寫入及更新的數據場景就比較有優勢了。
矛盾總是貫穿于事物的發展過程當中,有利就有弊。對于哈希存儲引擎也是如此,正是因為它的優勢而導致了一些不可避免的劣勢。首先、由于哈希存儲引擎的數據結構特點,那么對于一些數據內部字段之間以及數據本身有著相對復雜的關系的數據,比如二維表數據。哈希存儲引擎就會束手無策。其次,由于哈希存儲引擎的檢索算法是基于哈希索引表的哈希函數計算實現,那么它就只能實現一次比較孤立的數據定位,對于范圍的查詢以及檢索過程當中的一些排序、分組、過濾等操作就力不從心了。最后,還是從其數據增加、刪除、更新的算法來看。它是犧牲了大量的存儲空間來實現操作的高效性,那么后續必然會帶來空間的管理代價以及數據的合并處理代價,數據片越大、哈希樹的高度越高,那么數據檢索的效率相應會提高很多,因為哈希函數定位之后必然隨之而來的是對定位到的數據片的全部掃描,數據片越大,檢索的平均效率越差。同時后臺執行的數據片合并的時間越長。因此對于數據粒度比較大,又沒有一個好的哈希函數可以使用的場景,也不是哈希存儲引擎使用的最佳場景。
5. 總結與展望
無論是樹還是哈希存儲引擎,它們都是數據存取技術的設計思想,很多關系型數據庫大多基于B-Tree家族實現的,而很多分布式NoSQL數據庫都是基于Hash家族實現的,在每一種產品具體實現的過程中可能會改進其中的一些算法細節從而實現部分缺陷的優化,尤其是一些開源的數據庫。但是這種存儲引擎的基本思想是決定具體數據庫產品的適用場景的最根本原因,本文希望通過這些原理性的討論和分析展示給大家有一個宏觀的視圖,從而指導具體的數據庫設計實踐。當然也希望更多同業能從更多維?度繼續分析討論并分享。




















