日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

MySQL之B+树详解

發(fā)布時間:2023/12/16 数据库 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MySQL之B+树详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

理論是灰色的,實(shí)踐之樹長青🌲 ——恩格斯

概述

MySql這樣的關(guān)系型數(shù)據(jù)庫在查詢方面有一些重要特性,是KV型的數(shù)據(jù)庫或者 緩存所不具備的,比如:
(1)范圍查詢。
(2)前綴匹配模糊查詢。
(3)排序和分頁。

這些特性的支持,要?dú)w功于B+樹這種數(shù)據(jù)結(jié)構(gòu)。下面我們來分析一下B+樹是如何支持這些特性的。

邏輯結(jié)構(gòu)

這里我們拿數(shù)據(jù)庫主鍵對應(yīng)的B+樹邏輯結(jié)構(gòu)來說明,這個結(jié)構(gòu)有幾個關(guān)鍵特性:

  • 在葉子節(jié)點(diǎn)一層,所有記錄的主鍵按照從小到大的順序排 列,并且形成了一個雙向鏈表。葉子節(jié)點(diǎn)的每一個Key指向一條記錄。
  • 非葉子節(jié)點(diǎn)取的是葉子節(jié)點(diǎn)里面Key的最小值。這意味著所有 非葉子節(jié)點(diǎn)的Key都是冗余的葉子節(jié)點(diǎn)。同一層的非葉子節(jié)點(diǎn)也互相串 聯(lián),形成了一個雙向鏈表。

下面的結(jié)構(gòu)圖可以更好的說明這兩個特性:

基于這樣一個數(shù)據(jù)結(jié)構(gòu)以上特性就更好說明了:

  • 范圍查詢:比如要查主鍵在[1,17]之間的記錄。二次查詢,先查找 1所在的葉子節(jié)點(diǎn)的記錄位置,再查找17所在的葉子節(jié)點(diǎn)記錄的位置 (就是16所處的位置),然后順序地從1遍歷鏈表直到16所在的位置。
  • 前綴匹配模糊查詢:假設(shè)主鍵是一個字符串類型,要查詢where Key like abc%,其實(shí)可以轉(zhuǎn)化成一個范圍查詢Key in [abc,abcz]。當(dāng)然,如果是后綴匹配模糊查詢,或者諸如where Key like %abc%這樣的中間 匹配,則沒有辦法轉(zhuǎn)化成范圍查詢,只能挨個遍歷。
  • 排序與分頁:葉子節(jié)點(diǎn)天然是排序好的,支持排序和分頁。

另外,基于B+樹的特性,會發(fā)現(xiàn)對于offset這種特性,其實(shí)是用不到索引的。比如每頁顯示10條數(shù)據(jù),要展示第101頁,通常會寫成select xxx where xxx limit 1000, 10,從offset = 1000的位置開始取10條。 雖然只取了10條數(shù)據(jù),但實(shí)際上數(shù)據(jù)庫要把前面的1000條數(shù)據(jù)都遍 歷才能知道 offset =1000的位置在哪。對于這種情況,合理的辦法是不 要用offset,而是把offset = 1000的位置換算成某個max_id,然后用where 語句實(shí)現(xiàn),就變成了select xxx where xxx and id > max_id limit 10,這樣 就可以利用B+樹的特性,快速定位到max_id所在的位置,即是 offset=1000所在的位置。

物理結(jié)構(gòu)

上面的樹只是一個邏輯結(jié)構(gòu),最終要存儲到磁盤上。下面就以 MySQL中最常用的InnoDB引擎為例,看一下如何實(shí)現(xiàn)B+樹的存儲。

對于磁盤來說,不可能一條條地讀寫,而都是以“塊”為單位進(jìn)行讀 寫的。InnoDB默認(rèn)定義的塊大小是16KB,通過innodb_page_size參數(shù)指 定。這里所說的“塊”,是一個邏輯單位,而不是指磁盤扇區(qū)的物理塊。 塊是InnoDB讀寫磁盤的基本單位,InnoDB每一次磁盤I/O,讀取的都是 16KB的整數(shù)倍的數(shù)據(jù)。無論葉子節(jié)點(diǎn),還是非葉子節(jié)點(diǎn),都會裝在 Page里。InnoDB為每個Page賦予一個全局的32位的編號,所以InnoDB 的存儲容量的上限是64TB(2^30×16KB)。

16KB是一個什么概念呢?如果用來裝非葉子節(jié)點(diǎn),一個Page大概 可以裝1000個Key(16K,假設(shè)Key是64位整數(shù),8個字節(jié),再加上各種 其他字段),意味著B+樹有1000個分叉;如果用來裝葉子節(jié)點(diǎn),一個 Page大概可以裝200條記錄(記錄和索引放在一起存儲,假設(shè)一條記錄大概100個字節(jié))。基于這種估算,一個三層的B+樹可以存儲多少數(shù)據(jù) 量呢?如圖下圖所示:

  • 第一層:一個節(jié)點(diǎn)是一個Page,里面存放了1000個Key,對應(yīng)1000 個分叉。
  • 第二層:1000個節(jié)點(diǎn),1000個Page,每個Page里面裝1000個Key。
  • 第三層:1000×1000個節(jié)點(diǎn)(Page),每個Page里面裝200條記錄, 即是1000×1000×200 =2億條記錄,總?cè)萘渴?6KB×1000×1000,約16GB。

把第一層和第二層的索引全裝入內(nèi)存里,即(1+1000)×16KB,也 即約16MB的內(nèi)存。三層B+樹就可以支撐2億條記錄,并且一次基于主 鍵的等值查詢,只需要一次I/O(讀取葉子節(jié)點(diǎn))。由此可見B+樹的強(qiáng) 大!

基于Page,最終整個B+樹的物理存儲類似下圖所示:

Page與Page之間組成雙向鏈表,每一個Page頭部有兩個關(guān)鍵字段: 前一個Page的編號,后一個 Page 的編號。Page 里面存儲一條條的記 錄,記錄之間用單向鏈表串聯(lián),最終所有的記錄形成上面所示的雙向 鏈表的邏輯結(jié)構(gòu)。對于記錄來說,定位到了Page,也就定位到了Page里 面的記錄。因?yàn)镻age會一次性讀入內(nèi)存,同一個Page里面的記錄可以在 內(nèi)存中順序查找。

在InnoDB的實(shí)踐里面

  • 其中一個建議是按主鍵的自增順序插入記 錄,就是為了避免Page Split問題。比如一個Page里依次裝入了Key為(1,3,5,9)四條記錄,并且假設(shè)這個Page滿了。接下來如果插入一個 Key =4的記錄,就不得不建一個新的Page,同時把(1,3,5,9)分成兩半,前一半(1,3,4)還在舊的Page中,后一半(5,9)拷貝到新的Page 里,并且要調(diào)整Page前后的雙向鏈表的指針關(guān)系,這顯然會影響插入速 度。但如果插入的是Key = 10的記錄,就不需要做Page Split,只需要建 一個新的Page,把Key = 10的記錄放進(jìn)去,然后讓整個鏈表的最后一個 Page指向這個新的Page即可。
  • 另外一個點(diǎn),如果只是插入而不硬刪除記錄(只是軟刪除),也會 避免某個Page的記錄數(shù)減少進(jìn)而發(fā)生相鄰的Page合并的問題。

非主鍵索引

對于非主鍵索引,同上面類似的結(jié)構(gòu),每一個非主鍵索引對應(yīng)一顆 B+樹。在InnoDB中,非主鍵索引的葉子節(jié)點(diǎn)存儲的不是記錄的指針, 而是主鍵的值。所以,對于非主鍵索引的查詢,會查詢兩棵B+樹,先 在非主鍵索引的B+樹上定位主鍵,再用主鍵去主鍵索引的B+樹上找到 最終記錄。

有一點(diǎn)需要特別說明:對于主鍵索引,一個Key只會對應(yīng)一條記 錄;但對于非主鍵索引,值可以重復(fù)。所以一個Key可能對應(yīng)多條記 錄,如下表所示。假設(shè)對于字段1建立索引(字段1是一個字符類 型),一個A會對應(yīng)1,5,7三條記錄,C對應(yīng)8、12兩條記錄。這反映在 B+樹的數(shù)據(jù)結(jié)構(gòu)上面就是其葉子節(jié)點(diǎn)、非葉子節(jié)點(diǎn)的存儲結(jié)構(gòu),會和 主鍵索引的存儲結(jié)構(gòu)稍有不同。

主鍵ID字段1(非主鍵索引)其他字段
1A
5A
7A
8C
10B
12C

非主鍵索引的B+樹結(jié)構(gòu)如下圖所示:

首先,每個葉子節(jié)點(diǎn)存儲了主鍵的值;對于非葉子 節(jié)點(diǎn),不僅存儲了索引字段的值,同時也存儲了對應(yīng)的主鍵的最小值。

參考書籍:《軟件架構(gòu)設(shè)計(jì)》
個人github賬號:https://github.com/SpecialAll
歡迎一起交流學(xué)習(xí)!

總結(jié)

以上是生活随笔為你收集整理的MySQL之B+树详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。