為什么MySQL要用B+樹?聊聊B+樹與硬盤的前世今生【宇哥帶你玩轉MySQL 索引篇(二)】


為什么MySQL要用B+樹?聊聊B+樹與硬盤的前世今生

 

上一節,我們聊到數據庫為了讓我們的查詢加速,通過索引方式對數據進行冗余並排序,這樣我們在使用時就可以在排好序的數據里進行快速的二分查找,使得查詢效率指數提升。但是我在結尾同樣提到一個問題,就是內存大小一般是很有限的,不可能把一個表所有的數據都加載到內存中,那么我們該如何解決這個問題呢?在解決這個問題之前,需要先簡單了解一下硬盤知識

硬盤知識簡介

由於機械硬盤的高耐久,低成本,現在仍然是數據存儲的主流,所以這里着重討論機械硬盤,下面是一個機械硬盤結構圖

 

機械硬盤的數據都存放在盤片中,當我們從硬盤讀取數據時,我們需要提供一個地址,然后硬盤通過前后移動磁頭尋址,最后把地址對應數據返回。

這里有兩個過程很重要,一個是尋址,一個是讀取數據。以目前機械硬盤的速度,如果我們要從機械硬盤讀取一條1KB的數據大概只需要0.01ms(100MB/s),而尋址卻平均在10ms左右。通常我們把讀取一段連續的數據,不需要多次尋址的操作叫做順序讀,而讀取不連續的數據需要多次尋址的操作叫做隨機讀,用來區分它們之間的性能差距。

為了充分利用機械硬盤的性能,通常把相關數據連續保存,這樣就可以一次加載更多的數據,減少磁頭的的移動次數。操作系統有很多對此的優化,例如Linux ext3文件系統默認塊大小就是4kb。還有linux預加載能力,即當你頻繁訪問一塊數據時,系統會幫你把相鄰的數據也加載進來。

MySQL InnoDB與硬盤

了解完機械硬盤的基本知識,現在回到MySQL,MySQL InnoDB引擎也會把數據進行分塊存儲,默認是16KB。所以我們上一節中的索引結構圖在硬盤中的存儲就是每16KB為一個塊,當一個塊快存放快滿的時候開辟一個新的塊來存放。

books表為例

create table books(
    id int not null primary key auto_increment,
    name varchar(255) not null,
    author varchar(255) not null,
price decimal(12,2) not null, create_date
date not null, updated_at datetime not null default current_timestamp on update current_timestamp, index idx_books_create_date_price(create_date,price) )engine=InnoDB;

該表name字段的索引idx_books_create_date_price在硬盤中的存放就如下圖

 

 

 

 

當塊越來越多的時候,我們可能無法一次把所有的塊都加載到內存,此時就要對每個塊再進行索引,如下圖:

 

 

 

 

每個塊的上一級都存放着一條指向該塊的地址。這樣只需要加載頂部的第一塊,然后通過區間判斷就可以找到下一塊的地址。

例如我們查詢一條date=20210502的記錄,過程如下:

  1. 先從左邊頂部塊開始查找,發現"date=20210502"在"date=20210301"到"date=20210503"區間

  2. 加載區間對應的下一級塊

  3. 發現"date=20210502"在"date=20210403"與"date=20210602"區間,加載區間對應下一級塊

  4. 發現該塊為葉子節點,在該塊使用二分搜索進行查詢,最終找到記錄

如果把上圖旋轉一個,可以發現,整個圖就是一個樹,這其實就是B+樹。B+樹通過對數據塊進行索引,使得當數據量很大,無法一次全部加載到內存時,可以先加載一個表的頂部數據塊,然后根據數據所在區間再加載下一級的數據塊。這樣既保證了我們的快速搜索,又減少了內存使用。

MySQL InnoDB的聚簇索引和二級索引

了解了B+樹,現在就可以很容易區分MySQL的聚簇索引和二級索引。

聚簇索引就是用主鍵生成B+樹,在葉子節點存放這條記錄的完整信息

二級索引就是用索引行生成B+樹,在葉子節點只存放索引行和該行對應的主鍵信息

下面是聚簇索引和二級索引的區分圖

  

了解上面的知識,對於一個查詢,我們就可以大概想象出他的執行步驟

select * from books where name = "name400";

例如上面sql的執行步驟如下:

  1. 在二級索引idx_books_name索引中查找name="name400"的字段所對應的主鍵id

  2. 通過主鍵id在聚簇索引找到此id所對應的記錄

  3. 返回記錄中的所有字段

當我們select的字段在二級索引上不存在時,都需要使用聚簇索引回表查詢剩余字段。所以聚簇索引,也就是我們所說的id列,占用空間越小越好, 這樣就可以在一個節點中存放更多的id值,減少樹的層級,加速查詢效率。一般推薦主鍵使用int或者bigint而不是字符串。同時最好保證插入的id值為遞增的,這樣就不會造成在一個已經滿的節點中插入一條記錄造成頁分裂,降低查詢效率。

小結

這節我們先了解了硬盤的基礎知識,知道了機械硬盤的順序讀與隨機讀的巨大性能差距,以及操作系統為了優化磁盤性能而把數據進行按塊存儲。然后又學習了MySQL通過使用B+樹,把存放索引的多個數據塊進行索引,解決了我們上一節使用二分搜索需要先把所有數據都加載到內存的問題。最后,我們了解了聚簇索引和二級索引的區別,以及其中的使用建議。

下一節,我們會聊一聊如何創建一個好的索引,判斷一個索引的好壞標准有哪些。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM