mysql b 树原因_复习系列之数据库(四):MySQL为什么采用B+树作为索引结构?
MySQL中數(shù)據(jù)是索引組織表,即表中數(shù)據(jù)按照主鍵順序存放。所以就可以基于索引這種數(shù)據(jù)結(jié)構(gòu)實現(xiàn)一些高級算法,來提高檢索效率。
常見的查找算法
順序查找:復(fù)雜度O(n),在數(shù)據(jù)量大時,效率很低
二分查找:在有序為前提,復(fù)雜度O(logn)
hash查找:無法滿足范圍查找
二叉樹查找:O(logn),每個節(jié)點只能有一個左節(jié)點和一個右節(jié)點
試著用二叉樹來構(gòu)造一種索引方式
為什么MySQL沒有采用二叉樹來構(gòu)造索引呢?
由磁盤的物理結(jié)構(gòu)決定的。
首先,二叉樹中每個節(jié)點只能有一個左節(jié)點和一個右節(jié)點,直接導(dǎo)致樹的高度很高,邏輯上相近的節(jié)點,無法利用局部性,相鄰節(jié)點沒法直接通過指針相連,可能需要返回上層節(jié)點重復(fù)遞歸找到結(jié)果,效率低。
其次,當(dāng)數(shù)據(jù)量大時,內(nèi)存不夠,大部分?jǐn)?shù)據(jù)只能存放在磁盤上,只有在需要的時候才加載到內(nèi)存中,但是磁盤讀取速度顯然與內(nèi)存差很多,大部分時間會阻塞在IO上面,為了減少磁盤IO次數(shù),上面這些傳統(tǒng)的樹無法滿足。
磁盤結(jié)構(gòu)
—
下面是磁盤整體結(jié)構(gòu)示意圖
其中,磁盤可以轉(zhuǎn)動(各個磁盤必須同步轉(zhuǎn)動)。每個磁頭負(fù)責(zé)存取一個磁盤的內(nèi)容。磁頭不能轉(zhuǎn)動,但是可以沿磁盤半徑方向運動(實際是斜切向運動)。
盤片結(jié)構(gòu)示意圖
盤片被劃分成一系列同心環(huán),圓心是盤片中心,每個同心環(huán)叫做一個磁道,所有半徑相同的磁道組成一個柱面。
磁道被沿半徑線劃分成一個個小的段,每個段叫做一個扇區(qū),每個扇區(qū)是磁盤的最小存儲單元。
磁盤如何讀取數(shù)據(jù)?
磁盤如何可以提高IO效率?
為了提高效率,要盡量減少磁盤I/O,磁盤往往不是嚴(yán)格按需讀取,而是每次都會預(yù)讀,即使只需要一個字節(jié),磁盤也會從這個位置開始,順序向后讀取一定長度的數(shù)據(jù)放入內(nèi)存。由于磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉(zhuǎn)時間),因此對于具有局部性的程序來說,預(yù)讀可以提高I/O效率。
預(yù)讀的長度一般為頁(page)(在許多操作系統(tǒng)中,頁的大小通常為4k)的整倍數(shù)。主存和磁盤以頁為單位交換數(shù)據(jù)。
當(dāng)程序要讀取的數(shù)據(jù)不在主存中時,會觸發(fā)一個缺頁異常,此時系統(tǒng)會向磁盤發(fā)出讀盤信號,磁盤會找到數(shù)據(jù)的起始位置并向后連續(xù)讀取一頁或幾頁載入內(nèi)存中,然后異常返回,程序繼續(xù)運行。
為了減少磁盤 IO 次數(shù),B-/B+樹應(yīng)運而生。
B-/B+樹
—
B-樹
為了更快,B-樹每次將范圍分割為多個區(qū)間,區(qū)間越多,定位數(shù)據(jù)越快越精確。
所以新建節(jié)點時,直接申請頁大小的空間,計算機內(nèi)存分配是按頁對齊的,這樣就實現(xiàn)了一個節(jié)點只需要一次 IO。那么總 IO 的次數(shù)就縮減為了 log n 次。
下面是一棵簡化的B-樹:
B-樹的每個節(jié)點是 n 個有序的序列(a1,a2,a3…an),并將該節(jié)點的子節(jié)點分割成 n+1 個區(qū)間來進(jìn)行索引(X1< a1, a2 < X2 < a3, … , an+1 < Xn < anXn+1 > an)。每個 key 值緊跟著 data 域,B-樹的 key 和 data 是聚合在一起的。
B-樹的如何進(jìn)行查找的?
比如上圖中,若搜索 key 為 25 節(jié)點的 data,首先在根節(jié)點進(jìn)行二分查找(因為 keys 有序,二分最快),判斷 key 25 小于 key 50,所以定位到最左側(cè)的節(jié)點,此時進(jìn)行一次磁盤 IO,將該節(jié)點從磁盤讀入內(nèi)存,接著繼續(xù)進(jìn)行上述過程,直到找到該 key 為止。
B+樹
B+樹是B-樹的變種,它與B-樹的不同之處在于:
在B+樹中,key 副本存儲在內(nèi)部節(jié)點,真正的 key 和 data 存儲在葉子節(jié)點上 。
n 個 key 值的節(jié)點指針域為 n 而不是 n+1。
為了增加 區(qū)間訪問性,一般會對B+樹做一些優(yōu)化,增加了順序訪問指針。
下面是一棵簡化的B+樹:
因為內(nèi)節(jié)點并不存儲 data,所以一般B+樹的葉節(jié)點和內(nèi)節(jié)點大小不同,而B-樹的每個節(jié)點大小一般是相同的,為一頁。
因為增加了順序訪問指針,圖中如果要查詢key為從55到62的所有數(shù)據(jù)記錄,當(dāng)找到50后,只需順著節(jié)點和指針順序遍歷就可以一次性訪問到所有數(shù)據(jù)節(jié)點,極大提到了區(qū)間查詢效率。
B-樹與B+樹區(qū)別
1.B+樹內(nèi)節(jié)點不存儲數(shù)據(jù),所有 data 存儲在葉節(jié)點導(dǎo)致查詢時間復(fù)雜度固定為 log n。而B-樹查詢時間復(fù)雜度不固定,與 key 在樹中的位置有關(guān),最好為O(1)。
如上圖,如果查找50,剛好第一次就可以查找出來,所以最好情況是O(1)。由于B+樹所有的 data 域都在根節(jié)點,所以查詢 key 為 50的節(jié)點必須從根節(jié)點索引到葉節(jié)點,時間復(fù)雜度固定為 O(log n)。
2.B+樹葉節(jié)點兩兩相連可大大增加區(qū)間訪問性,可使用在范圍查詢等,而B-樹每個節(jié)點 key 和 data 在一起,則無法區(qū)間查找。
B+樹可以很好的利用局部性原理,若我們訪問節(jié)點 key為 50,則 key 為 55、60、62 的節(jié)點將來也可能被訪問,我們可以利用磁盤預(yù)讀原理提前將這些數(shù)據(jù)讀入內(nèi)存,減少了磁盤 IO 的次數(shù)。當(dāng)然B+樹也能夠很好的完成范圍查詢。比如查詢 key 值在 50-70 之間的節(jié)點。
3.B+樹更適合外部存儲。由于內(nèi)節(jié)點無 data 域,每個節(jié)點能索引的范圍更大更精確
由于B-樹節(jié)點內(nèi)部每個 key 都帶著 data 域,而B+樹節(jié)點只存儲 key 的副本,真實的 key 和 data 域都在葉子節(jié)點存儲。由于磁盤 IO 數(shù)據(jù)大小是固定的,在一次 IO 中,單個元素越小,量就越大。這就意味著B+樹單次磁盤 IO 的信息量大于B-樹,從這點來看B+樹相對B-樹磁盤 IO 次數(shù)少。
MySQL索引實現(xiàn)
—
在MySQL中,索引屬于存儲引擎級別的概念,不同存儲引擎對索引的實現(xiàn)方式是不同的,本文主要討論MyISAM和InnoDB兩個存儲引擎的索引實現(xiàn)方式。
索引分為:
聚集索引(Primary key):按照每張表的主鍵構(gòu)造一棵B+樹,同時葉子節(jié)點中存放的即為整張表的行記錄數(shù)據(jù),也將聚集索引的葉子節(jié)點稱為數(shù)據(jù)頁。每個數(shù)據(jù)頁都通過一個雙向鏈表來進(jìn)行鏈接。每張表只能擁有一個聚集索引。
輔助索引(Secondary key):葉子節(jié)點并不包含行記錄的全部數(shù)據(jù)。葉子節(jié)點除了包含鍵值以外,還包含一個書簽,該書簽找到與索引相對應(yīng)的行數(shù)據(jù)。每張表可以有多個輔助索引。
MyISAM引擎的實現(xiàn)
下圖是MyISAM索引的原理圖:
在MyISAM中,主索引和輔助索引在結(jié)構(gòu)上沒有任何區(qū)別,只是主索引要求key是唯一的,而輔助索引的key可以重復(fù)。如果我們在Col2上建立一個輔助索引,則此索引的結(jié)構(gòu)如下圖所示:
InnoDB引擎的實現(xiàn)
而在InnoDB中,表數(shù)據(jù)文件本身就是按B+Tree組織的一個索引結(jié)構(gòu),這棵樹的葉節(jié)點data域保存了完整的數(shù)據(jù)記錄。
這個索引的key是數(shù)據(jù)表的主鍵,因此InnoDB表數(shù)據(jù)文件本身就是主索引。
下圖是InnoDB主索引示意圖:
可以看到葉節(jié)點包含了完整的數(shù)據(jù)記錄。這種索引叫做聚集索引。因為InnoDB的數(shù)據(jù)文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有)。
下圖為定義在Col3上的一個輔助索引:
這里以英文字符的ASCII碼作為比較準(zhǔn)則。輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。
這里就很容易明白為什么不建議使用過長的字段作為主鍵,因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。
非單調(diào)的主鍵會造成在插入新記錄時數(shù)據(jù)文件為了維持B+Tree的特性而頻繁的分裂調(diào)整,十分低效,而使用自增字段作為主鍵則是一個很好的選擇。
數(shù)據(jù)的查找
—
1、精確查找:?select * from user_info where id = 23
內(nèi)存中找到根節(jié)點,根節(jié)點二分查找確定范圍,逐層向下查找,讀取葉子節(jié)點,通過二分查找找到記錄或未命中。
2、索引范圍查找:select * from user_info where id >= 18 and id < 22
內(nèi)存中找到根節(jié)點,確定索引定位條件id=18,找到滿足條件第一個葉節(jié)點
,順序掃描所有結(jié)果,直到終止條件滿足id >=22
3、全表掃描:select * from user_info where name = 'abc'
直接讀取葉節(jié)點頭結(jié)點, 順序掃描, 返回符合條件記錄, 到最終節(jié)點結(jié)束
4、二級索引查找:Select * from table_x where name = “d”;
id是主索引,name是二級索引。
通過二級索引查出對應(yīng)主鍵,拿主鍵回表查主鍵索引得到數(shù)據(jù), 二級索引可篩選掉大量無效記錄,提高效率。
參考文獻(xiàn):1、《MySQL技術(shù)內(nèi)幕》
總結(jié)
以上是生活随笔為你收集整理的mysql b 树原因_复习系列之数据库(四):MySQL为什么采用B+树作为索引结构?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: run till exit from #
- 下一篇: nginx nodejs环境配置_服务器