關係型資料庫在查詢方面有一些重要特性,是kv型的資料庫或者快取所不具備的,比如:
(1)範圍查詢。
(2)字首匹配模糊查詢。
(3)排序和分頁。
這些特性的支援,要歸功於b+樹這種資料結構。下面來分析b+樹是如何支援這些查詢特性的。
圖6-1展示了資料庫的主鍵對應的b+樹的邏輯結構,這個結構有幾個關鍵特徵:
(1)在葉子節點一層,所有記錄的主鍵按照從小到大的順序排列,並且形成了乙個雙向鍊錶。葉子節點的每乙個key指向一條記錄。
(2)非葉子節點取的是葉子節點裡面key的最小值。這意味著所有非葉子節點的key都是冗餘的葉子節點。同一層的非葉子節點也互相串聯,形成了乙個雙向鍊錶。
圖6-1 資料庫的主鍵對應的b+樹的邏輯結構
基於這樣乙個資料結構,要實現上面的幾個特性就很容易了:
(1)範圍查詢:比如要查主鍵在[1,17]之間的記錄。二次查詢,先查詢1所在的葉子節點的記錄位置,再查詢17所在的葉子節點記錄的位置(就是16所處的位置),然後順序地從1遍歷鍊錶直到16所在的位置。
(2)字首匹配模糊查詢。假設主鍵是乙個字串型別,要查詢where key like abc%,其實可以轉化成乙個範圍查詢key in [abc,abcz]。當然,如果是字尾匹配模糊查詢,或者諸如where key like %abc%這樣的中間匹配,則沒有辦法轉化成範圍查詢,只能挨個遍歷。
(3)排序與分頁。葉子節點天然是排序好的,支援排序和分頁。
另外,基於b+樹的特性,會發現對於offset這種特性,其實是用不到索引的。比如每頁顯示10條資料,要展示第101頁,通常會寫成select *** where *** limit 1000, 10,從offset = 1000的位置開始取10條。
雖然只取了10條資料,但實際上資料庫要把前面的1000條資料都遍歷才能知道offset = 1000的位置在哪。對於這種情況,合理的辦法是不要用offset,而是把offset = 1000的位置換算成某個max_id,然後用where語句實現,就變成了select *** where *** and id > max_id limit 10,這樣就可以利用b+樹的特性,快速定位到max_id所在的位置,即是offset=1000所在的位置。
上面的樹只是乙個邏輯結構,最終要儲存到磁碟上。下面就以mysql中最常用的innodb引擎為例,看一下如何實現b+樹的儲存。
對於磁碟來說,不可能一條條地讀寫,而都是以「塊」為單位進行讀寫的。innodb預設定義的塊大小是16kb,通過innodb_page_size引數指定。這裡所說的「塊」,是乙個邏輯單位,而不是指磁碟扇區的物理塊。塊是innodb讀寫磁碟的基本單位,innodb每一次磁碟i/o,讀取的都是16kb的整數倍的資料。無論葉子節點,還是非葉子節點,都會裝在page裡。innodb為每個page賦予乙個全域性的32位的編號,所以innodb的儲存容量的上限是64tb(2316kb)。
16kb是乙個什麼概念呢?如果用來裝非葉子節點,乙個page大概可以裝1000個key(16k,假設key是64位整數,8個位元組,再加上各種其他字段),意味著b+樹有1000個分叉;如果用來裝葉子節點,乙個page大概可以裝200條記錄(記錄和索引放在一起儲存,假設一條記錄大概100個位元組)。基於這種估算,乙個三層的b+樹可以儲存多少資料量呢?如圖6-2所示。
第一層:乙個節點是乙個page,裡面存放了1000個key,對應1000個分叉。
第二層:1000個節點,1000個page,每個page裡面裝1000個key。
第三層:10001000個節點(page),每個page裡面裝200條記錄,即是10001000200 = 2億條記錄,總容量是16kb10001000,約16gb。
把第一層和第二層的索引全裝入記憶體裡,即(1+1000)16kb,也即約16mb的記憶體。三層b+樹就可以支撐2億條記錄,並且一次基於主鍵的等值查詢,只需要一次i/o(讀取葉子節點)。由此可見b+樹的強大!
基於page,最終整個b+樹的物理儲存類似圖6-3所示。
page與page之間組成雙向鍊錶,每乙個page頭部有兩個關鍵字段:前乙個page的編號,後乙個page的編號。page裡面儲存一條條的記錄,記錄之間用單向鍊錶串聯,最終所有的記錄形成圖6-1所示的雙向鍊錶的邏輯結構。對於記錄來說,定位到了page,也就定位到了page裡面的記錄。因為page會一次性讀入記憶體,同乙個page裡面的記錄可以在記憶體中順序查詢。
圖6-2 三層的磁碟b+樹示意圖
圖6-3 b+樹物理儲存示意圖
在innodb的實踐裡面,其中乙個建議是按主鍵的自增順序插入記錄,就是為了避免page split問題。比如乙個page裡依次裝入了key為(1,3,5,9)四條記錄,並且假設這個page滿了。接下來如果插入乙個key = 4的記錄,就不得不建乙個新的page,同時把(1,3,5,9)分成兩半,前一半(1,3,4)還在舊的page中,後一半(5,9)拷貝到新的page裡,並且要調整page前後的雙向鍊錶的指標關係,這顯然會影響插入速度。但如果插入的是key = 10的記錄,就不需要做page split,只需要建乙個新的page,把key = 10的記錄放進去,然後讓整個鍊錶的最後乙個page指向這個新的page即可。
另外乙個點,如果只是插入而不硬刪除記錄(只是軟刪除),也會避免某個page的記錄數減少進而發生相鄰的page合併的問題。
對於非主鍵索引,同上面類似的結構,每乙個非主鍵索引對應一顆b+樹。在innodb中,非主鍵索引的葉子節點儲存的不是記錄的指標,而是主鍵的值。所以,對於非主鍵索引的查詢,會查詢兩棵b+樹,先在非主鍵索引的b+樹上定位主鍵,再用主鍵去主鍵索引的b+樹上找到最終記錄。
有一點需要特別說明:對於主鍵索引,乙個key只會對應一條記錄;但對於非主鍵索引,值可以重複。所以乙個key可能對應多條記錄,如表6-2所示。假設對於欄位1建立索引(欄位1是乙個字元型別),乙個a會對應1,5,7三條記錄,c對應8、12兩條記錄。這反映在b+樹的資料結構上面就是其葉子節點、非葉子節點的儲存結構,會和主鍵索引的儲存結構稍有不同。
表6-2 非主鍵索引字段值重複
如圖6-4所示,首先,每個葉子節點儲存了主鍵的值;對於非葉子節點,不僅儲存了索引欄位的值,同時也儲存了對應的主鍵的最小值。
圖6-4 非主鍵索引b+樹示意圖
資料庫原理知識 B 樹 B 樹 B 樹
b 樹 是一種多路搜尋樹 並不是二叉的 1.定義任意非葉子結點最多只有m個兒子 且m 2 2.根結點的兒子數為 2,m 3.除根結點以外的非葉子結點的兒子數為 m 2,m 4.每個結點存放至少m 2 1 取上整 和至多m 1個關鍵字 至少2個關鍵字 5.非葉子結點的關鍵字個數 指向兒子的指標個數 1...
資料庫設計原理 B樹 B 樹 B 樹
b樹即二叉搜尋樹 1.所有非葉子結點至多擁有兩個兒子 left和right 2.所有結點儲存乙個關鍵字 3.非葉子結點的左指標指向小於其關鍵字的子樹,右指標指向大於其關鍵字的子樹 b樹的搜尋,從根結點開始,如果查詢的關鍵字與結點的關鍵字相等,那麼就命中 否則,如果查詢關鍵字比結點關鍵字小,就進入左兒...
打造先進的記憶體KV資料庫 1 B樹索引的建立(1)
在搜尋引擎的設計中,往往需要使用倒排索引,在當前記憶體 不斷走低的情況下,記憶體資料庫必然會成為主流。kv資料庫由於適合map reduce用於分布式處理。本系統設計實現如下目標 實現極高效能的查詢 實現分布式集群儲存 實現可靠的日誌系統 索引採用b數索引,這樣做的目的是大大利用cpu的快取,讓每個...