散列表?B-樹?B+樹?原來是這麼個玩意

散列表

散列表(也稱哈希表)是根據關鍵碼值(Key value)而直接進行訪問的數據結構,它讓碼值經過哈希函數的轉換映射到散列表對應的位置上,查找效率非常高。哈希索引就是基於散列表實現的,假設我們對名字建立了哈希索引,則查找過程如下圖所示:

 

對於每一行數據,存儲引擎都會對所有的索引列(上圖中的 name 列)計算一個哈希碼(上圖散列表的位置),散列表裏的每個元素指向數據行的指針,由於索引自身只存儲對應的哈希值,所以索引的結構十分緊湊,這讓哈希索引查找速度非常快!但是哈希索引也有它的劣勢,如下:

  1. 針對哈希索引,只有精確匹配索引所有列的查詢纔有效,比如我在列(A,B)上建立了哈希索引,如果只查詢數據列 A,則無法使用該索引
  2. 哈希索引並不是按照索引值順序存存儲的,所以也就無法用於排序,也就是說無法根據區間快速查找
  3.  哈希索引只包含哈希值和行指針,不存儲字段值,所以不能使用索引中的值來避免讀取行
  4. 哈希索引只支持等值比較查詢,包括 =,IN(),不支持任何範圍的查找,如 age > 17


在 InnoDB 引擎中,有一種特殊的功能叫「自適應哈希索引」,如果 InnoDB 注意到某些索引列值被頻繁使用時,它會在內存基於 B+ 樹索引之上再創建一個哈希索引,這樣就能讓 B+樹也具有哈希索引的優點,比如快速的哈希查找


跳錶


跳錶,是基於鏈表實現的一種類似“二分”的算法,它可以快速的實現增,刪,改,查操作。

簡單地說,跳錶是在鏈表之上加上多層索引構成的

先來看一下單向鏈表如何實現查找

當我們要在該單鏈表中查找某個數據的時候需要的時間複雜度爲O(n)

如果我們給該單鏈表加一級索引,將會改善查詢效率

如圖所示,當我們每隔一個節點就提取出來一個元素到上一層,把這一層稱作索引,其中的down指針指向原始鏈表。

當我們查找元素16的時候,單鏈表需要比較10次,而加過索引的兩級鏈表只需要比較7次。當數據量增大到一定程度的時候,效率將會有顯著的提升

跳錶的查詢時間複雜度可以達到O(logn)

如果我們再加多幾級索引的話,效率將會進一步提升。這種鏈表加多級索引的結構,就叫做跳錶


B-樹

平衡多路查找樹

what ??? 平衡@#¥%樹?  穩住,能贏!!!

首先我們先看一下什麼是平衡二叉樹?

平衡二叉樹又稱爲AVL樹,是一種特殊的二叉排序樹

emmmmm..... 什麼又是二叉排序樹(BST樹)????

我們來看一下二叉排序樹的特性(⚠️ 二叉排序樹可以是空樹):

  • 若它的左子樹不空,則左子樹上所有關鍵字的值均小於根關鍵字的值
  • 若它的右子樹不空,則右子樹上所有關鍵字的值均大於根關鍵字的值
  • 左右子樹又各是一棵二叉排序樹

好,瞭解完特性,我們來看看二叉排序樹到底長什麼樣字?

二叉查找樹可以任意地構造,同樣是2,3,5,6,7,8這六個數字,也可以按照下圖的方式來構造:

但是這棵二叉樹的查詢效率就低了(二叉排序樹越矮查找效率越高)。因此若想二叉樹的查詢效率儘可能高,需要這棵二叉樹是平衡的,從而引出新的定義——平衡二叉樹,或稱AVL樹

AVL樹(平衡二叉樹)有以下特性:

  •  首先它是一個二叉排序樹,具有二叉排序樹的全部性質
  • 其次,其左右子樹都是平衡二叉樹,且左右子樹高度之差的絕對值不超過1

一句話概括就是:平衡二叉樹是一種特殊的二叉排序樹,以樹中所有結點爲根的樹的左右子樹高度之差的絕對值不能超過1

最終,平衡二叉樹它是長這樣的:

好,知道了二叉排序樹(BST樹),平衡二叉樹(AVL樹)後,我們看一下主角 B-樹 (平衡多路查找樹)

B-樹中,含有最多分支數的結點,該結點分支樹的總數就是B-樹的階,通常用m表示,從查找效率考慮,要求m>=3

一棵m階的B-樹或者一棵空樹,或者是滿足以下要求的m叉樹

  1. 每個結點最多有m個分支(實際爲指向子樹的指針,下文用P表示),而最少分支數要看是否爲根結點,如果是根結點且不是葉子結點,則至少有兩個分支,非根非葉子結點至少有⌈m/2⌉個分支(⌈⌉爲向上取整符號)
  2. 每個結點n個關鍵字(下文用K表示),由於關鍵字的個數等於分支數減一,即 n = m - 1,所以在B-樹中,根結點關鍵字的取值範圍爲1 <= n <= m-1,除根結點外,其他結點中關鍵字的取值範圍爲⌈m/2⌉ - 1 <= n <= m - 1
  3.  Kℹ(1 <= i <= n)爲關鍵字,結點內各關鍵字互不相等,且按從小到大排序,即 Kℹ < K(ℹ+1)
  4. Pℹ(0 <= i <=n)爲指向子樹根節點的指針,且滿足P(ℹ-1)(1 <= i <= n-1)指向的子樹的所有節點關鍵字均大於Kℹ且小於K(ℹ+1),當然Po所指結點上的關鍵字小於K1,Pn所指結點上的關鍵字大於Kn
  5. 所有葉子結點都在同一層

瞭解了B-樹的特性後,是不是很好奇B-樹長啥挫樣?吶~看下面:

沒錯,這就是一個3階的B-樹(爲什麼是3階你明白了嗎?)

通過上面的圖我們可以看到:

  • 每個節點佔用一個==磁盤塊==的磁盤空間,指針存儲的是子節點所在磁盤塊的地址
  • 每個節點中不僅包含數據的鍵值,還有對應的數據記錄


模擬查找關鍵字29的過程:

- 根據根節點找到磁盤塊1,讀入內存。【磁盤I/O操作第1次】
- 比較關鍵字29在區間(17,35),找到磁盤塊1的指針P2。
- 根據P2指針找到磁盤塊3,讀入內存。【磁盤I/O操作第2次】
- 比較關鍵字29在區間(26,30),找到磁盤塊3的指針P2。
- 根據P2指針找到磁盤塊8,讀入內存。【磁盤I/O操作第3次】
- 在磁盤塊8中的關鍵字列表中找到關鍵字29。

從上面的過程可以看出,整個 查找需要3次磁盤I/O操作,和3次內存查找操作。由於內存中的關鍵字是一個有序表結構,可以利用二分法查找提高效率。而3次磁盤I/O操作是影響整個B-Tree查找效率的決定因素。B-Tree相對於AVLTree(平衡二叉樹)縮減了節點個數,使每次磁盤I/O取到內存的數據都發揮了作用,從而提高了查詢效率


通過上面的圖,有個概念叫磁盤塊

系統從磁盤讀取數據到內存時是以磁盤塊(block)爲基本單位的,位於同一個磁盤塊中的數據會被一次性讀取出來,而不是需要什麼取什麼

那什麼叫頁(Page)呢?

頁是InnoDB存儲引擎中的概念,頁是其磁盤管理的最小單位,InnoDB存儲引擎中默認每個頁的大小爲16KB

在計算機裏,無論是內存還是磁盤,操作系統都是按頁的大小進行讀取的,磁盤每次讀取都會預讀,會提前將連續的數據讀入內存中,這樣就避免了多次 IO,這就是計算機中有名的局部性原理

即我用到一塊數據,很大可能這塊數據附近的數據也會被用到,乾脆一起加載,省得多次 IO 拖慢速度,以此提高查詢效率
這個連續數據有多大呢,這個連續數據就是 MySQL 的頁,默認值爲 16 KB,頁大小並不是越大越好,InnoDB 是通過內存中的緩存池(pool buffer)來管理從磁盤中讀取的頁數據的頁太大的話,很快就把這個緩存池撐滿了,可能會造成頁在內存與磁盤間頻繁換入換出,影響性能

B+樹

B+樹是在B-樹的基礎上的一種優化,
使其更適合實現外存儲索引結構,InnoDB存儲引擎就是用B+Tree實現其索引結構

B+樹相對B-樹有幾點不同:

  1. 有n個關鍵字的結點就有n個分支(指針)
  2. 每個結點(除根結點外)中的關鍵字個數n的取之範圍爲⌈m/2⌉ <= n <= m,根結點的取值範圍爲2 <= n <= m
  3. 非葉子結點只存儲鍵值信息,即所有非葉子結點僅起到一個索引的作用,而數據記錄都存放在葉子結點中
  4. 在B+樹上有一個指針指向關鍵字最小的葉子結點,所有葉子結點之間都有一個鏈指針,鏈接成一個線性鏈表

下面看一下B+樹的容顏:


B-Tree結構圖中可以看到每個節點中不僅包含數據的key值,還有data值。而每一個頁的存儲空間是有限的,如果data數據較大時將會導致每個結點(即一個頁)能存儲的key的數量很小,當存儲的數據量很大時同樣會導致B-Tree的深度較大,增大查詢時的磁盤I/O次數,進而影響查詢效率。在B+Tree中,所有數據記錄節點都是按照鍵值大小順序存放在同一層的葉子結點上,而非葉子結點上只存儲key值信息,這樣可以大大加大每個節點存儲的key值數量,降低B+Tree的高度

InnoDB存儲引擎中,一個16KB大小的頁可以存多少條數據呢?
一般表的主鍵類型爲INT(佔用4個字節)或BIGINT(佔用8個字節),指針類型也一般爲4或8個字節,也就是說一個頁,中大概存儲16KB/(8B+8B)=1K個鍵值(因爲是估值,爲方便計算,這裏的K取值爲〖10〗^3)。

也就是說一個深度爲3的B+Tree索引可以維護10^3 * 10^3 * 10^3 = 10億 條記錄

 

頁分裂 與 頁合併

B+ 樹爲了維護索引的有序性,每插入或更新一條記錄的時候,會對索引進行更新

以3階B+樹爲例,

假設當某個結點的關鍵字總數爲3(以達到最大關鍵字數量),如果恰好需要再往該結點上插入關鍵字時,顯然就不再符合B+ 樹條件,這時就會造成頁分裂,以調整這個節點以讓它符合B+ 樹條件。頁分裂造成的調整必然導致性能的下降

什麼時候會發生頁合併呢?答案是在刪除記錄的時候,當刪除表記錄的時候,索引也要刪除。此時就有可能發生頁合併

 

爲什麼官方建議使用自增長主鍵作爲索引?

結合B+Tree的特點,自增主鍵是連續的,在插入過程中儘量減少頁分裂,即使要進行頁分裂,也只會分裂很少一部分。並且能減少數據的移動,每次插入都是插入到最後。總之就是減少分裂和移動的頻率

 

爲什麼索引結構默認使用B-Tree,而不是hash,二叉樹,紅黑樹?

  • hash:雖然可以快速定位,但是沒有順序,IO複雜度高。
  • 二叉樹:樹的高度不均勻,不能自平衡,查找效率跟數據有關(樹的高度),並且IO代價高。
  • 紅黑樹:樹的高度隨着數據量增加而增加,IO代價高。


以上就是關於散列表,B-樹,B+樹的總結,如有錯誤之處,歡迎大家指正,討論!


【參考文檔】
1. https://mp.weixin.qq.com/s?__biz=MzI5MTU1MzM3MQ==&mid=2247484006&idx=1&sn=3e15abeb5299a3e9b578332dd8565273&scene=21#wechat_redirect
2. https://blog.csdn.net/sinat_32176267/article/details/85460695
3. https://www.cnblogs.com/liqiangchn/p/9060521.html
4. << 數據庫結構高分筆記 >> 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章