python3的pyqt5 qtablewidget按数据大小排列_InnoDB为什么要选择B+树来存储数据?
關(guān)于InnoDB索引,我們可能知道InnDB索引是用B+樹實現(xiàn)的,而B+樹就是一種能優(yōu)化查詢速度的數(shù)據(jù)結(jié)構(gòu)。但我們又沒想過這樣一個問題,能優(yōu)化查詢速度的數(shù)據(jù)結(jié)構(gòu)有很多,為什么InnoDB要采用B+樹?
常見優(yōu)化查詢速度數(shù)據(jù)結(jié)構(gòu)
哈希表
哈希表是一種以鍵 - 值(key-value)存儲數(shù)據(jù)的結(jié)構(gòu),我們只要輸入待查找的鍵即 key,就可以找到其對應(yīng)的值即 Value。哈希的思路很簡單,把值放在數(shù)組里,用一個哈希函數(shù)把 key 換算成一個確定的位置,然后把 value 放在數(shù)組的這個位置。
不可避免地,多個 key 值經(jīng)過哈希函數(shù)的換算,會出現(xiàn)同一個值的情況。處理這種情況的一種方法是,拉出一個鏈表。
假設(shè)現(xiàn)在維護著一個身份證信息和姓名的表,需要根據(jù)身份證號查找對應(yīng)的名字,這時對應(yīng)的哈希索引的示意圖如下所示:
圖中,User2 和 User4 根據(jù)身份證號算出來的值都是 N,但沒關(guān)系,后面還跟了一個鏈表。假設(shè),這時候你要查 IDcardn2 對應(yīng)的名字是什么,處理步驟就是:首先,將 IDcardn2 通過哈希函數(shù)算出 N;然后,按順序遍歷,找到 User2。
需要注意的是,圖中四個 IDcardn 的值并不是遞增的,這樣做的好處是增加新的 User 時速度會很快,只需要往后追加。但缺點是,因為不是有序的,所以哈希索引做區(qū)間查詢的速度是很慢的。
可以設(shè)想下,如果現(xiàn)在要找身份證號在[IDcardX, IDcardY]這個區(qū)間的所有用戶,就必須全部掃描一遍了。
所以,哈希表這種結(jié)構(gòu)適用于只有等值查詢的場景,比如 Memcached 及其他一些 NoSQL 引擎。
有序數(shù)組
有序數(shù)組在等值查詢和范圍查詢場景中的性能就都非常優(yōu)秀。還是上面這個根據(jù)身份證號查名字的例子,如果我們使用有序數(shù)組來實現(xiàn)的話,示意圖如下所示:
這里我們假設(shè)身份證號沒有重復(fù),這個數(shù)組就是按照身份證號遞增的順序保存的。這時候如果你要查 IDcardn2 對應(yīng)的名字,用二分法就可以快速得到,這個時間復(fù)雜度是 O(log(N))。
同時很顯然,這個索引結(jié)構(gòu)支持范圍查詢。要查身份證號在[IDcardX, IDcardY]區(qū)間的 User,可以先用二分法找到 IDcardX(如果不存在 IDcardX,就找到大于 IDcardX 的第一個 User),然后向右遍歷,直到查到第一個大于 IDcardY 的身份證號,退出循環(huán)。
如果僅僅看查詢效率,有序數(shù)組就是最好的數(shù)據(jù)結(jié)構(gòu)了。但是,在需要更新數(shù)據(jù)的時候就麻煩了,往中間插入一個記錄就必須得挪動后面所有的記錄,成本太高。
所以,有序數(shù)組索引只適用于靜態(tài)存儲引擎,比如要保存的是 2017 年某個城市的所有人口信息,這類不會再修改的數(shù)據(jù)。
二叉搜索樹
上面根據(jù)身份證號查名字的例子,如果我們用二叉搜索樹來實現(xiàn)的話,示意圖如下所示:
二叉搜索樹的特點是:每個節(jié)點的左兒子小于父節(jié)點,父節(jié)點又小于右兒子。這樣如果你要查 IDcardn2 的話,按照圖中的搜索順序就是按照 UserA -> UserC -> UserF -> User2 這個路徑得到。這個時間復(fù)雜度是 O(log(N))。
當然為了維持 O(log(N)) 的查詢復(fù)雜度,就需要保持這棵樹是平衡二叉樹。為了做這個保證,更新的時間復(fù)雜度也是 O(log(N))。
樹可以有二叉,也可以有多叉。多叉樹就是每個節(jié)點有多個兒子,兒子之間的大小保證從左到右遞增。二叉樹是搜索效率最高的,但是實際上大多數(shù)的數(shù)據(jù)庫存儲卻并不使用二叉樹。其原因是,索引不止存在內(nèi)存中,還要寫到磁盤上。
可以想象一下一棵 100 萬節(jié)點的平衡二叉樹,樹高 20。一次查詢可能需要訪問 20 個數(shù)據(jù)塊。在機械硬盤時代,從磁盤隨機讀一個數(shù)據(jù)塊需要 10 ms 左右的尋址時間。也就是說,對于一個 100 萬行的表,如果使用二叉樹來存儲,單獨訪問一個行可能需要 20 個 10 ms 的時間,這個查詢可真夠慢的。
為了讓一個查詢盡量少地讀磁盤,就必須讓查詢過程訪問盡量少的數(shù)據(jù)塊。那么,我們就不應(yīng)該使用二叉樹,而是要使用“N 叉”樹。這里,“N 叉”樹中的“N”取決于數(shù)據(jù)塊的大小。
以 InnoDB 的一個整數(shù)字段索引為例,這個 N 差不多是 1200。這棵樹高是 4 的時候,就可以存 1200 的 3 次方個值,這已經(jīng) 17 億了。考慮到樹根的數(shù)據(jù)塊總是在內(nèi)存中的,一個 10 億行的表上一個整數(shù)字段的索引,查找一個值最多只需要訪問 3 次磁盤。其實,樹的第二層也有很大概率在內(nèi)存中,那么訪問磁盤的平均次數(shù)就更少了。
N 叉樹由于在讀寫上的性能優(yōu)點,以及適配磁盤的訪問模式,已經(jīng)被廣泛應(yīng)用在數(shù)據(jù)庫引擎中了。
數(shù)據(jù)庫引擎常用數(shù)據(jù)結(jié)構(gòu)
B樹
B樹也稱B-樹,它是一顆多路平衡查找樹,B樹和后面講到的B+樹也是從最簡單的二叉樹變換而來的,并沒有什么神秘的地方,下面我們來看看B樹的定義。
每個節(jié)點最多有m-1個關(guān)鍵字(可以存有的鍵值對)。
根節(jié)點最少可以只有1個關(guān)鍵字。
非根節(jié)點至少有m/2個關(guān)鍵字。
每個節(jié)點中的關(guān)鍵字都按照從小到大的順序排列,每個關(guān)鍵字的左子樹中的所有關(guān)鍵字都小于它,而右子樹中的所有關(guān)鍵字都大于它。
所有葉子節(jié)點都位于同一層,或者說根節(jié)點到每個葉子節(jié)點的長度都相同。
每個節(jié)點都存有索引和數(shù)據(jù),也就是對應(yīng)的key和value。
B+樹
B+樹其實和B樹是非常相似的,我們首先看看相同點。
相同點:?根節(jié)點至少一個元素 非根節(jié)點元素范圍:m/2 <= k <= m-1
不同點:?B+樹有兩種類型的節(jié)點:內(nèi)部結(jié)點(也稱索引結(jié)點)和葉子結(jié)點。內(nèi)部節(jié)點就是非葉子節(jié)點,內(nèi)部節(jié)點不存儲數(shù)據(jù),只存儲索引,數(shù)據(jù)都存儲在葉子節(jié)點。內(nèi)部結(jié)點中的key都按照從小到大的順序排列,對于內(nèi)部結(jié)點中的一個key,左樹中的所有key都小于它,右子樹中的key都大于等于它。葉子結(jié)點中的記錄也按照key的大小排列。?每個葉子結(jié)點都存有相鄰葉子結(jié)點的指針,葉子結(jié)點本身依關(guān)鍵字的大小自小而大順序鏈接。?父節(jié)點存有右孩子的第一個元素的索引。
總結(jié)
B和B+樹的區(qū)別在于,B+樹的非葉子結(jié)點只包含導(dǎo)航信息,不包含實際的值,所有的葉子結(jié)點和相連的節(jié)點使用鏈表相連,便于區(qū)間查找和遍歷。
B+ 樹的優(yōu)點在于:
由于B+樹在內(nèi)部節(jié)點上不包含數(shù)據(jù)信息,因此在內(nèi)存頁中能夠存放更多的key。數(shù)據(jù)存放的更加緊密,具有更好的空間局部性。因此訪問葉子節(jié)點上關(guān)聯(lián)的數(shù)據(jù)也具有更好的緩存命中率。
B+樹的葉子結(jié)點都是相鏈的,因此對整棵樹的便利只需要一次線性遍歷葉子結(jié)點即可。而且由于數(shù)據(jù)順序排列并且相連,所以便于區(qū)間查找和搜索。而B樹則需要進行每一層的遞歸遍歷。相鄰的元素可能在內(nèi)存中不相鄰,所以緩存命中性沒有B+樹好。
但是B樹也有優(yōu)點,其優(yōu)點在于,由于B樹的每一個節(jié)點都包含key和value,因此經(jīng)常訪問的元素可能離根節(jié)點更近,因此訪問也更迅速。
往期推薦
深入學(xué)習(xí)G1垃圾收集器
Redisson分布式鎖自動續(xù)期源碼分析
spring中發(fā)布訂閱模式實踐
總結(jié)
以上是生活随笔為你收集整理的python3的pyqt5 qtablewidget按数据大小排列_InnoDB为什么要选择B+树来存储数据?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 07/11/10 资料整理
- 下一篇: python中isinstance(3、