MySql的資料儲存之B+樹(淺談)

~java小白~發表於2023-05-13

一.MySql的實際儲存位置

B+樹是MySql資料結構的主流儲存方式,包括InnoDB和MYISAM引擎,它們的預設儲存結構都是B+樹

瞭解B+樹前,我們先要知道MySql 的實際儲存位置在哪?

有人會說它存在我麼的D盤或C盤的MySql資料夾的Data目錄裡,這個回答沒錯,我們在深入的瞭解一下呢?

不管是在個人電腦上使用本機MySql或者在網際網路上把資訊存在伺服器上,其實它們最終的儲存地址都是被寫在了物理磁碟上,只有存在物理磁碟上,才能保證資料長久不丟失

物理磁碟一般可以描述為:柱面,磁面,扇區,透過這三個引數可以精準定位到資料所在的地方

我們來看看一個普通的磁面:

 

我們可以看到這個磁面上記錄著磁軌和扇區,

一般磁碟定位有固定頭和移動頭兩種:

固定頭每個磁軌上都有一個讀寫頭,造價高,但是讀寫速度快,定位時間短

移動頭每個磁面上一個讀寫頭,造價適中,讀寫速度主要取決於定位時間,從定位磁軌,再到定位扇區所化的時間,現行的物理磁碟大多都是使用的移動頭定位

我們在瞭解了我們的磁碟和移動頭後,我們就需要了解一次物理磁碟的I/O,它指的是對於磁碟來說,一次磁碟的連續讀或者連續寫稱為一次磁碟 I/O, 磁碟的 IOPS 就是每秒磁碟連續讀次數和連續寫次數之和。

我們這裡就把他當作讀寫一次扇區,即一次I/O只讀寫一個扇區(實際上的I/O指的是根據查詢的資料大小,可能會連續讀寫好幾個或者幾百個扇區,但是也只進行了一次I/O,因為I/O讀寫的時間開銷最大的還是在移動頭的定位時間上)

一個扇區的大小比較公認的是512位元組(後面慢慢發展的也有一個扇區2048位元組的,我們這裡就舉例512位元組)

我們先簡單的看一個資料表,資料在扇區上是怎麼儲存的:

 這裡我們定義了一張表,還有裡面的資料,他有三個屬性:id 8位元組,name  40位元組,no  16位元組

由此我們可以推出:這張表的一條記錄就是8+40+16=64位元組,我們這會兒定義的這張表的一條記錄就要佔64位元組

一個扇區512位元組:512/64=8

所以一個扇區就只能裝8條記錄,我們這32條記錄就需要32/8=4,就需要4個扇區去裝入,

如果我們按照規定的一次I/O讀寫一個扇區,那我們要找到32這條記錄的話需要4次I/O操作,4次還算一般效能,但是我們在大型的資料庫儲存一張表可不止32條記錄哦

但我們簡單的把記錄加到800條的時候:800/8 = 100 ,也就是需要100個扇區來裝入,那我們要查詢第800條記錄的時候就需要100次I/O操作,顯然這樣的I/O操作就太慢了,要是有1000個人需要查詢,時間開銷就很大了

這種情況我們就需要引入索引;B+樹的根節點幾乎全是索引

二.MySql的索引

什麼是索引呢?

索引是為了加速對錶中資料行的檢索而建立的一種分散的儲存結構。索引是針對表而建立的,它是由資料頁面以外的索引頁面組成的,每個索引頁面中的行都會含有邏輯指標,以便加速檢索物理資料。

簡單的說,索引就是建表或者後期加入的,它可以分為主鍵索引,複合索引,普通索引,

當我們建立索引以後,就會自動的把建立索引的屬性或屬性組單獨的拿出來然後和這個屬性所在的實體地址一起單獨構成一個表來存放,雖然多了一個表但是因為屬性較少,所佔的空間少,查詢效率高了

這裡我們再來簡單的看兩個表:

 這裡我們可以看到右邊的表就是我們的資料表,它記錄著800條記錄,上面我們也看到了它要被查詢到的話需要100次I/O,所以我們考慮為id建一個索引

當id被選為索引以後,id會和id所在的實際實體地址構成一個表,這個表id  8位元組,地址 8位元組,一共16位元組,它也是存在一個扇區的還有可能沒有和資料原表在相鄰的扇區

一個扇區的的大小512位元組:512/16 = 32,所以這個索引表所在的扇區,可以裝32條記錄

第一條記錄:記錄id為1,它所在的實體地址,

第二條記錄:記錄id為33,它所在的實體地址,

第三十二條記錄:記錄id為1023,它所在的實體地址

有了這個索引表之後,我們再查800條記錄資料的過程就簡單化了

首先,一次I/O操作會把這個索引表查出來(要用到索引才能命中,也就是再sql語句中的條件判斷語句帶有id),這裡只進行了一次I/O就拿到了1~33,33~65,....~1023這些區間的資訊

然後,我們查第800條記錄的時候我們很幸運,因為索引表中有一個區間是800~833就可以透過地址直接去讀800所在的扇區 這裡就也只用了一次I/O操作

這是最好的情況:2次I/O就拿到了第800條件記錄

 

我們看看最壞的情況,細心的朋友可以發現再每個區間都有一個最壞情況,

比如:

1~33區間上,當我們要查詢32時只能透過找到id=1的扇區,然後依次讀取直到讀到id=32的扇區

33~65區間上,當我們要查詢64時只能透過找到id=33的扇區,然後依次讀取直到讀到id=64的扇區

......

800~833區間上,我們去查詢832時:

只會給我們找到800的首地址,這是一次I/O讀寫了

從800讀到832一共是32條記錄:32/8=4(扇區),也就是我們要連續讀取4個扇區才可以找到id = 832這條記錄,這就需要4次I/O

我們可以看到,即使是最壞的情況:1+4=5,我們也只需要5次I/O就可以拿到任意的資料了

比起沒有索引的查詢:從第32條記錄開始,就會成比例的不斷增大讀寫次數

有索引的情況的查詢:最壞的情況也就5次I/O操作,但是索引需要單獨儲存,總的來說還是利大於弊,不建議建立索引,因為索引的維護需要開銷,一般只會給經常查詢且不常更改的資料做索引

繼續加大資料,這次存放80000條記錄:

當資料量達到80000條的時候:我們的一級索引表(緊貼實際資料表的索引表被稱為一級索引)就顯得的很乏力了,

我們簡單的計算一下,我們的一級索引表只能存放1~1025記錄的區間地址,當資料為80000

80000/1024 = 78~79,我們需要79個扇區來儲存

如果是最壞的情況:我們要查詢第80000條記錄,那麼在一級索引表中我們就進行了79次I/O操作了,再加上一級索引表去查實際資料的地址又是4次I/O,所以在這種情況下就是79+4=83次I/O

對於80000條資料查詢就需要83次I/O,很顯然,這個I/O次數太多了,我們得想辦法把I/O次數壓下去

 這個時候我們的開發者想到的就是加一層,二級索引來指向一級索引,一級索引指向資料,對於B+樹結構來說就是多了一層

 在二級索引上的adress欄位就是記錄的一級索引的地址,而不直接指向資料,在二級索引就是記錄一個一級扇區的位置,

比如:

一級索引一個扇區的資料區間在1~1025

二級索引一條記錄的就是一級索引區間的1~1025

 所以一個二級索引扇區可以記錄一級索引區間的範圍是:

1024 * 32 = 32768

對於有80000條記錄的表,二級索引扇區只需要3個就可以放下

這個時候利用二級索引查詢80000:

1.

首先我們要找到80000這個區間,他在第三個扇區上,那麼在二級索引上的I/O讀寫就需要  3次

然後去一級索引上找,由於是二級索引提供的實體地址,只需要一次I/O就可以讀取到那個扇區,一級索引上有一個區間就是80000~81025,

最後去找實際的資料剛好80000就是頭指標,也只需要一次I/O就可以完成

這是最好的一種情況:3+1+1=5,最好的狀態5次I/O就可以拿到資料了

 2.

最壞的情況也是出現在一級索引去找資料:就是80000~80033這個區間

我們要找80032的時候,只能透過首地址為80000的地址去找,要連續查詢4個扇區,就是4次I/O操作才能拿到

3+1+4=8

所以在B+樹上加了一層以後最壞,不管什麼情況下,查詢任意資料都只需要8次I/O操作,

綜上:查詢80000條資料(最壞的情況)

a.沒有索引,一個扇區一個扇區的查詢,80000/8 = 10000,沒有索引需要10000次I/O操作

b.一級索引,由於第80000條記錄在最後一個扇區上,80000/1024 = 79 ,一級索引需要79次I/O,利用一級索引去查詢資料4次I/O,一級索引需要83次I/O操作

c.二級索引,由於第80000條記錄在最後一個扇區上,80000 / 32768 = 3,二級索引需要3次I/O,利用二級索引的區間地址去找一級索引只需要1次I/O,然後一級索引去找資料需要4次I/O,二級索引查詢資料就只需要8次I/O操作

到了這裡我們就可以很明顯的感受到索引的魅力了,其實講到這裡我們也可以很明顯看到我們的B+樹是怎麼組織資料和索引的了

我們把上面的圖片順時針翻轉90度,就可以很明顯的看到我們的B+樹就是這個模型

可能對部落格中的例子存在的疑問:

a.索引表的區間範圍一定是32嗎?

不是,要根據開發人員的預設設定,或者後期資料庫管理員去調整,一般人是更改不了物理儲存的,只能瞭解原理

b.索引的表每條記錄都是16位元組嗎?

不是,要根據建立索引屬性的大小和實際實體地址大小而定,不一定每個扇區都能存剛好存32條索引記錄

c.800條記錄沒有索引的情況真的要100次I/O操作嗎?

不是,要根據一次讀取的頁面大小,MySql的預設讀取是16KB:16kb / 64 =250 條記錄,一般的查詢一次I/O至少就是250條記錄,

根據計算機區域性性原理,當一個扇區的資料被使用後,它周圍的資料極有可能再最近會被用到,所以mysql的快取機制會支援一次查詢返回4頁,即從查詢的當前的頁開始往後磁頭移動4頁的大小,4*16=64kb

也就是64kb / 64 = 100條記錄,在快取中有一次I/O是有100條記錄被返回,然後怎麼查詢就是InnoDB引擎所要了解的知識了

 三.MySql中B+樹索引儲存結構的形成

現在我簡化一下那張圖片,然後把他旋轉90度,我們再來看看它是什麼樣的圖形

 

這就是一顆較為完整的B+樹了,我們在仔細的看看可以發現B+樹的特徵

a.索引區間由根節點不斷的變小,描述資料所在的扇區也越來越細化

b.根節點存放的都是索引,只有最下面一層的子節點才是資料

我們看完了B+樹的形成過程以及它的大致模型,我們就不得不提一下它和B樹的區別了,這是更深瞭解B+樹的關鍵,

現在圖片上描述的B+樹,其實B樹有很大一部分都能做到,我們就來看看B樹和B+樹的區別吧

B樹和B+樹的區別(主要的區別)

區別1:

B樹的在根節點也存有資料,然後根節點儲存的資料又是索引,又是資料

 如圖,在索引上出現的資料就不會在子節點出現了

可以說父節點可能既有索引又有資料,這就是B樹的儲存方式

B+樹根節點沒有資料,只存放索引

 如圖,資料都在最下面一層的子節點上,除了這一層以外的父節點全是索引沒有資料

總結:

B樹,存在一種情況就是所查的資料在最上層,因為節點有資料可能第80000條記錄在最上層區間上,所以一次I/O就可以拿到資料,最壞的情況就和B+樹一樣,要到最後一層才能拿到資料

B+樹,每次拿資料都需要在最下一層去拿,即使我們去拿一個表的第一條記錄,它也會從根結點一級一級的往下直到最後一層才有資料

感覺有時候B樹比B+樹快啊,為什麼MySql要用B+樹呢?

因為B+樹的查詢比B樹更加穩定,有利於開發者,資料庫管理員對其進行效能測試,執行時日誌檢測的資料也會很準確

舉個簡單的例子,就以上面兩張圖片:我們要查詢第7條資料和第8條資料,這兩條記錄是緊挨著的

使用B樹查詢:

第七條記錄:1次I/O

第八條記錄:2次I/O

使用B+樹查詢:

第七條記錄:2次I/O

第八條記錄:2次I/O

我們可以看到使用B樹的第一次查詢需要1次I/O,第二個需要2次I/O,B+樹則都需要兩次

這就很明顯阿可以看出B樹的查詢效率很不穩定,尤其是層數變多,資料區間變得的時候,就更加明顯

區別2:

這個區別是B+樹特有的,

在B樹上子節點和子節點是相互隔離開的,現在我們構造一種情況,B樹中我們一條查詢語句需要查詢兩條記錄,

比如    id = 6   and  id = 8

由於這是一條查詢語句,所以要一起執行會發生什麼,我們的磁頭會先移動到根節點進行定位,然後就可以id = 6這條記錄,然後從新去根節點查索引,磁頭再定位到 id = 8 這條記錄

很顯然我們的B樹查詢進行了兩次磁頭定位,我們知道在讀寫磁碟時,最大的開銷就是磁頭的定位,所以我們要儘量少的進行磁頭定位

所以,B+樹在最後一層的資料結點上都加了指標(指向的是實體地址),讓它指向下一個區間的開始位置,把尾部的資料又串起來了,就是專門對付這種相鄰區間要重複磁頭定位的情況

 有了這個尾部指標,我們在用B+樹去查詢 id =6 和 id = 8的記錄,當經過一次磁頭定位以後找到了id = 6 的記錄,它會根據尾指標直接讀取 id = 8的記錄,加快了相鄰扇或幾個相鄰扇區的讀寫速度

四.總結

上面所有的描述都只是B+樹常規的資料儲存方式,實際上MySql的執行儲存比B+樹要複雜的多,因為我們各自的裝置或者後期對物理儲存的預設引數不一樣

都會導致B+樹儲存的不同

需要真正的就業或者更進一步學習,MySql的認識還有很長的一段路要走

 

相關文章