深入淺出:Mysql索引底層原理

一 索引的概念簡介

對於一個開發,數據庫是大家離不開的工具,特別是mysql,在目前很多中國的互聯網企業大多選擇mysql作爲存數數據庫。由此,數據庫索引的正確使用對於每位工程師尤其是後端工程師來說是必須要掌握的技能。

那麼什麼是數據庫的索引呢?上大學時老師大多是這樣講的:索引就是數據庫的目錄。細細品味這個定義,或許能夠形象的說明索引在數據庫的作用,但是看完大家往往還是不夠清楚索引到底是個啥。

我們先來思考一個問題,假設沒有任何索引,我們的SQL怎樣去查找數據呢?

create table ‘student’

id name age
1 小明 23
2 小紅 24
3 小綠 25
4 小藍 26
5 小黑 27
6 小藍 28
7 小黑 29

比如上面這張表,假設執行select * from student where name = ‘小藍’;

那麼mysql只能從第一條數據開始遍歷,找到第一條數據的name字段,和要查詢的‘小藍’進行比較,如果是則返回一條數據並接着比較第二條,如果不是就繼續比較第二條,直到把表內所有數據都輪循一遍最終才能拿到結果。

上述過程在數據量比較小的時候我們或許能夠接受,但如果是上百萬甚至上千萬的數據,輪循過程是非常長的,需要的時間也是不可接受的,所以我們必須需要一種方式來快速查詢我們需要的數據,而我們較易想到的就是各種各樣的數據結構幫助我們把數據排個序,以方便我們快速查找,索引由此而來。

索引是幫助MySQL高效獲取數據的排好序的數據結構

二 爲什麼是B+ Tree

首先給大家推薦一個非常好用的網站,數據結結構可視化工具https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

上面說到索引是一種排好序的數據結構,那怎樣的數據結構才能最好的支持對數據的高效訪問呢?

說到數據結構,我們馬上能想到的是各種樹形結構,二叉樹,紅黑樹等等,下面我們來探究下這幾種樹的存儲。

1,二叉樹

二叉樹非常簡單,就是每個節點最多有兩個子樹的樹結構,遵循右邊的節點比左邊的節點數字更大。假設按照數據ID的順序對上面的表的age字段建立索引,二叉樹結構如下
在這裏插入圖片描述
可以看到所有數據都集中在了右子樹上,也是要通過輪詢去查找,跟沒有建索引相差不大,顯然簡單的二叉樹結構並不合適。

2,紅黑樹

紅黑樹是一顆自平衡二叉查找樹,也是我們常見的數據結構,JAVA8中的HashMap就是用紅黑樹進行了優化,大大增加了查找效率。假設我們用紅黑樹來存儲上面的age索引字段
在這裏插入圖片描述
如果我們要查找age=29的,只需要查找4次,已經比二叉樹進步了很多。

現在假設有數以百萬的數據,2^20≈104 0000,紅黑樹的高度h>20了,形成了一棵很高的樹。如果要查找葉子節點上的數字,經過的查找次數大於20次了,效率也並不高

3,B-Tree

怎樣減小樹的高度h呢?如果在每一個結點位置多放幾個數據,並且每個數據位置都有左右兩顆子樹,這樣樹的每一層就會多存儲很多數據。

BTree是平衡搜索多叉樹,設樹的度爲m(m叉樹,m>=2),高度爲h,則BTree要滿足以下條件:

  • 樹中任何一個結點最多含有m個孩子;
  • 每個非葉子節點由n個指針pointn-1個key組成,其中ceil(m/2)<=n<=m,ceil是向上取整函數,key和point相互間隔,結點兩端一定是key;
  • 每個葉子節點的高度都相同,等於h,葉子節點的指針都是null
  • 非葉子節點的key都是[key,data]二元組,其中key表示索引值,data是索引所在行數據或者磁盤地址
  • 所有的索引key元素不重複

在這裏插入圖片描述
簡化一下的圖如下
在這裏插入圖片描述

在BTree的機構下,使用二分查找方式,查找複雜度爲h*log(n),一般來說樹的高度是很小的(三中會做具體探討),一般爲3左右,所以BTtree是一個十分高效的查找結構。

對於BTree和下面的B+Tree的查找,增加,和刪除過程不做具體贅述,如需要可參考https://blog.csdn.net/endlu/article/details/51720299

4,B+Tree

B樹還有優化的空間,而mysql索引實際上使用的是B+Tree。

B+Tree是BTree的一個變種:

  • B+Tree的非葉子節點不存儲數據值data,只存儲健值,這樣同樣空間可以存儲更多的健值
  • 葉子節點沒有指針,所有的鍵值(包括父節點已經有的鍵值)都會在葉子節點上,同時會對應存儲data,行數據或者數據地址值
  • 非葉子節點由n個鍵值keyn個指針point組成
  • 所有的索引key元素有冗餘(下圖的最左邊的15在每一層級都有)
  • 葉子節點有橫向指針連接,大大提高區間訪問速度
    在這裏插入圖片描述

對於橫向指針,我們在進行區間查找(>,<,<=,>=,between and )時也是非常希望用到索引的,這時橫向指針就可以方便的取到一整個區間的索引值。這一點也是hash索引沒有被廣泛應用的原因(無法支持區間查找)。

三 如何做到支持千萬級表查詢

我們知道的是數據庫存儲的數據都是存儲在磁盤上的,而索引也同樣是會存儲在磁盤上,只是會把根節點數據預加載到內存裏。sql通過根節點先查找數據,再根據指針point將其子節點加載到內存進行查找,直到找到葉子節點的data。

mysql會預設定每一個結點的佔用空間,可以通過如下sql查詢:
在這裏插入圖片描述

如圖所示,mysql預設值的索引結點的大小約爲16kb

這一點也足以說明B+Tree會比BTree更加適合索引,因爲BTree的每一層上的每一個葉子節點上都要存儲point和key(包含data),某些情況下data是每一行的全部數據,會佔用比較大的存儲空間,導致葉子節點不能存儲更多的索引,而B+Tree每一行只有pioint和索引值,可以儘可能多的分佈索引值。

下面我們以主鍵索引爲例,來看下一顆h=3的B+Tree可以存儲多少數據:

假設id是bigint類型,拿一個id主鍵索引佔用8字節(b)的存儲空間,一個point指針大約佔用6b存儲空間,也就是說對於非葉子節點,一條主鍵索引的存儲大約要佔用14b的存儲空間。

上面已經達到葉子節點大小爲16kb,每個索引佔用14b,可以得到每個非葉子節點大約可以存儲1170個索引數據。

由於葉子節點要存儲data,我們假設最壞的情況,data存儲的是整行數據(不同的存儲引擎不一樣),每行大約佔1kb的空間,那每個葉子節點能存儲16個數據。

在這裏插入圖片描述

則綜上,h=3時,一顆B+Tree大約能存儲1170 × 1170 × 16 ≈ 2100 萬 條數據

可以看到B+Tree只用高度3就存下了2000w的數據索引,這是非常有意義的。上面提到mysql不會把索引的全部數據結構加載到內存,而是根據查到的point一層一層加載,更小的高度h就意味着加載的次數更少,磁盤I/O更少,這一點也是提升查找速度的關鍵。

另外值得注意的是,B+Tree對於每條數據的查詢速度是非常穩定的。因爲非葉子結點不存儲數據data,所有查詢都會最終查詢到葉子結點,而葉子結點的高度都相等,因此所有數據查詢速度會大體相等。

四 myIsam和Innodb存儲引擎的索引實現

我們知道數據都是存儲在磁盤上的,那是以怎樣的形式存儲的呢?來看下面的圖
(存儲引擎的區別是對於每張表而言的,這一點需要清楚)

在這裏插入圖片描述

innodb存儲引擎有frm和ibd2個文件
myisam存儲引擎有frm,MYD,MYI3個文件

見名知意,frm即frame,框架,存儲的是我們的建表語句DDL;
myisam的MYD,MYI,D即Data,I即index,就是表的數據和索引;
而在innodb中數據和索引一同存儲到ibd文件中了

由此我們何以引出一個重要的概念,聚集索引和非聚集索引,實際上聚集索引就是將索引和數據存儲到了一個文件中,而非聚集索引就是將索引和數據分開存儲。

myisam存儲引擎

在這裏插入圖片描述

myisam存儲引擎存儲索引具有如下特點:

  • 非聚集索引,索引和數據存儲在兩個文件中
  • 葉子節點存儲的是這條數據在數據文件(MYD)中的物理地址
  • 主鍵索引和輔助索引的存儲形式幾乎是一樣的,主鍵索引不重複,不能爲null

在使用myisam存儲引擎的表中根據索引查找數據的順序如下:
根結點(常駐內存)查找到指針point —》 根據point I/O下一層結點,查找到point —》I/O葉子結點的數據到內存,查找到磁盤文件地址—》MYD文件中查找到需要的行數據

innodb存儲引擎

在這裏插入圖片描述
innodb存儲引擎索引具有以下特點:

  • 表數據文件本身就是按照B+Tree組織的一個索引結構文件(ibd)文件;
  • 主索引是聚集索引,葉子結點存儲的是健值對應的數據本身data,輔助索引存儲的葉子結點存儲的是鍵值對應的數據的主鍵健值。因此,主鍵要選擇合適的類型和長度,類型越簡單越好,長度越小越好;
  • 輔助索引中葉子節點存儲主鍵的好處,一致性,行移動和頁分列式不需要再改變葉子結點的存儲方式;
    更少的存儲空間,不需要存儲整行數據;
  • innodb存儲引擎中必有主鍵索引,即使沒有顯示建立,mysql也會自己建立主鍵索引;
  • 聚集索引的數據是根據主鍵的順序保存的,因此推薦使用整型的自增ID,如果不是自增,B+Tree可能需要進行復雜的調整以保證符合整個B+Tree的特點;

使用innodb查詢數據時,如果是根據主鍵索引,就直接在主鍵聚集索引的B+Tree可以查找到葉子結點的整行數據;如果是輔助索引,則先在輔助索引的B+Tree上查找到主鍵id,再渠道主鍵的B+Tree上查找到整行數據。

兩者對比

在這裏插入圖片描述
簡單的總結對比一下:
1,使用主鍵索引查詢時,聚集索引只需查找一次,而非聚集索引需要查找兩次,會啓用更多的磁盤I/O
2,葉子結點而論,聚集索引要比非聚集索引佔用更多的空間。這樣會導致聚集索引再插入數據時比非聚集索引慢,因爲需要檢測主鍵是否重複,需要遍歷所有的葉子結點,佔用空間少的就會有更少的磁盤I/O
3,聚簇索引存儲的是主鍵id,數據順序和索引本身的順序相同;非聚集索引的主索引和輔助索引,索引的順序和數據物理順序無關

五 複合索引原理

在這裏插入圖片描述
如圖,聯合索引就是將多個索引一起放到了B+Tree的結點上作爲key,並且嚴格按照建索引是的順序排列。例如建立(a,b,c)三列聯合索引,那會先根據a索引排列,再根據b索引排列,最後是c。如上圖,上面結點是先按照10002,10004排列,左下結點在10001仙童的情況下,在按照A,E,S排列,右下結點前兩列索引都相同,再按照日期排列。

那麼現在你應該可以試着解釋爲什麼聯合索引遵循最左前綴原則了。

淺出總結:

1,索引是幫助MySQL高效獲取數據的排好序的數據結構;
2,mysql採用B+Tree作爲底層索引的數據結構,因爲它能在很小的高度下存儲千萬級的數據
3,myisam使用非聚集索引,索引文件和數據文件分開存儲,葉子結點保存數據地址
innodb主索引使用聚集索引,表數據文件本身就是按照B+Tree組織的一個索引結構文件,葉子結點存儲整行數據,輔助索引的葉子結點存儲主鍵值
4,橫向指針是爲了方便區間查找
5,聯合索引按順序將多列數據存儲到一個結點的一個索引key值上

歡迎關注個人公衆號一起學習:
在這裏插入圖片描述

發佈了5 篇原創文章 · 獲贊 4 · 訪問量 2253
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章