一文帶你深入理解Mysql索引底層數(shù)據(jù)結(jié)構(gòu)與算法

理解索引的特性

  • 索引是幫助Mysql高效獲取數(shù)據(jù)排好序數(shù)據(jù)結(jié)構(gòu)
  • 索引是存儲在文件里面的

索引的各種存儲結(jié)構(gòu)及優(yōu)缺點

首先看一下,在數(shù)據(jù)庫沒有加索引的情況下,SQL中的where語句是如何查找目標記錄的,首先看到下圖的Col2字段,如果我們要查找where col2 = 89的記錄,我們在沒有加索引的情況下,數(shù)據(jù)庫默認會從上往下按順序查找記錄,那么將會查找5次才能查到數(shù)據(jù),如果對Col2字段加上索引之后,假設(shè)使用最簡單的二叉樹作為索引存儲,那么帶條件查詢的話,就只需要查詢2次即可查到了,效率有明顯的提升

二叉樹

二叉樹的特點

  • 至少有一個節(jié)點(根節(jié)點)
  • 每個節(jié)點最多有兩顆子樹,即每個節(jié)點的度小于3。
  • 左子樹和右子樹是有順序的,次序不能任意顛倒。
  • 即使樹中某節(jié)點只有一棵子樹,也要區(qū)分它是左子樹還是右子樹。

優(yōu)點:
二叉樹是一種比順序結(jié)構(gòu)更加高效地查找目標元素的結(jié)構(gòu),它可以從第一個父節(jié)點開始跟目標元素值比較,如果相等則返回當前節(jié)點,如果目標元素值小于當前節(jié)點,則移動到左側(cè)子節(jié)點進行比較,大于的情況則移動到右側(cè)子節(jié)點進行比較,反復(fù)進行操作最終移動到目標元素節(jié)點位置。

缺點:
在大部分情況下,我們設(shè)計索引時都會在表中提供一個自增整形字段作為建立索引的列,在這種場景下使用二叉樹的結(jié)構(gòu)會導(dǎo)致我們的索引總是添加到右側(cè),在查找記錄時跟沒加索引的情況是一樣的,如下圖

紅黑樹

紅黑樹是一種自平衡二叉搜索樹(BST),且紅黑樹節(jié)點遵循以下規(guī)則:

  • 每個節(jié)點只能是紅色或黑色
  • 根節(jié)點肯定是黑色的
  • 紅色節(jié)點的父或子節(jié)點都必然是黑色的(兩個紅色的節(jié)點不會相連)
  • 任一節(jié)點到其所有后代NULL節(jié)點的每條路徑都具有相同數(shù)量的黑色節(jié)點
  • 每個Null節(jié)點都是黑色的

優(yōu)點:
紅黑樹也叫平衡二叉樹,它不僅繼承了二叉樹的優(yōu)點,而且解決了上面二叉樹遇到的自增整形索引的問題,從下面的動態(tài)圖中可以看出紅黑樹會走動對結(jié)構(gòu)進行調(diào)整,始終保證`左子節(jié)點數(shù) < 父節(jié)點數(shù) < 右子節(jié)點數(shù)的規(guī)則。

缺點:
在數(shù)據(jù)量大的時候,深度也很大。從圖中可以看出每個父節(jié)點只能存在兩個子節(jié)點,如果我們有很多數(shù)據(jù),那么樹的深度依然會很大,可能就會超過十幾二十層以上,對我們的磁盤尋址不利,依然會花費很多時間查找。

Hash

優(yōu)點:
對數(shù)據(jù)進行Hash(散列)運算,主流的Hash算法有MD5、SHA256等等,然后將哈希結(jié)果作為文件指針可以從索引文件中獲得數(shù)據(jù)的文件指針,再到數(shù)據(jù)文件中獲取到數(shù)據(jù),按照這樣的設(shè)計,我們在查找where Col2 = 22的記錄時只需要對22做哈希運算得到該索引所對應(yīng)那行數(shù)據(jù)的文件指針,從而在MySQL的數(shù)據(jù)文件中定位到目標記錄,查詢效率非常高。很多時候Hash索引要比B+樹索引更高效,但是僅能滿足 "=" 或 "IN" 不支持范圍查詢(因為Hash沖突問題)

缺點:
無法解決范圍查詢(Range)的場景,比如 select count(id) from sus_user where id >10;因此Hash這種索引結(jié)構(gòu)只能針對字段名=目標值的場景使用。
不適合模糊查詢(like)的場景。

B-Tree

既然紅黑樹存在缺點,那么我們可以在紅黑樹的基礎(chǔ)上構(gòu)思一種新的儲存結(jié)構(gòu)。解決的思路也很簡單,既然覺得樹的深度太長,就只需要適當?shù)卦黾用總€樹節(jié)點能存儲的數(shù)據(jù)個數(shù)即可,但是數(shù)據(jù)個數(shù)也必須要設(shè)定一個合理的閾值,不然一個節(jié)點數(shù)據(jù)個數(shù)過多會產(chǎn)生多余的消耗。
按照這樣的思路,我們先來了解下關(guān)于B-Tree的一些知識點:

  • 度(Degree)-節(jié)點的數(shù)據(jù)存儲個數(shù),每個樹節(jié)點中數(shù)據(jù)個數(shù)大于 15/16*Degree(未驗證) 時會自動分裂,調(diào)整結(jié)構(gòu)
  • 葉節(jié)點具有相同的深度,左子樹跟右子樹的深度一致
  • 葉節(jié)點的指針為空
  • 節(jié)點中的數(shù)據(jù)key從左到右遞增排列

樹節(jié)點結(jié)構(gòu):
在這里需要說明下的是,BTree的結(jié)構(gòu)里每個節(jié)點包含了索引值和表記錄的信息,我們可以按照Map集合這樣理解:key=索引,value=表記錄,如下圖所示:

優(yōu)點:
BTree的結(jié)構(gòu)可以彌補紅黑樹的缺點,解決數(shù)據(jù)量過大時整棵樹的深度過長的問題。相同數(shù)量的數(shù)據(jù)只需要更少的層,相同深度的樹可以存儲更多的數(shù)據(jù),查找的效率自然會更高。

缺點:
從上面得知,在查詢單條數(shù)據(jù)是非常快的。但如果范圍查的話,BTree結(jié)構(gòu)每次都要從根節(jié)點查詢一遍,效率會有所降低,因此在實際應(yīng)用中采用的是另一種BTree的變種B+Tree(B+樹)。

B+Tree

  • 非葉子節(jié)點不存儲data值,只存儲冗余索引,可以放更多索引
  • 葉子節(jié)點包含所有索引字段
  • 葉子節(jié)點用指針連接、提高區(qū)間訪問的性能

運行原理:假如我要查找id為30的數(shù)據(jù)

1.首先會再根目錄進行查找,將該數(shù)據(jù)頁加載到內(nèi)存通過二分查找等其他合適的算法,發(fā)現(xiàn)30是在15-56之間,則進入第二數(shù)據(jù)頁
2.進入之后,又會將該數(shù)據(jù)頁加載到內(nèi)存比較
3.最后到了葉子節(jié)點,加載到內(nèi)存查找到了30,直接返回

如果要查詢id>30的數(shù)據(jù)

1.會先定位30的數(shù)據(jù)
2.因為葉子節(jié)點的指針鏈路是排好序的,所以會直接根據(jù)30的索引值,往右的所有值查詢出來

為什么mysql使用b+樹而不使用b樹

影響樹的查詢效率是根據(jù)樹高度決定的,樹越高查詢越慢
樹的每個節(jié)點的大小大概為16kb
1.b+樹非葉子節(jié)點,只存索引key,而這個key一般只會占用8個字節(jié),那么就意味著,千萬級別的數(shù)據(jù),我存冗余索引key,也就可以越多,且高度并不會高
2.b樹,非葉子節(jié)點和葉子節(jié)點,都會存儲key和data值,這樣的話,每個非葉子節(jié)點能夠存儲的容量就非常少,樹的節(jié)點就會層層疊高,效率也就很慢
總結(jié):
1.b+樹,只有葉子節(jié)點存儲數(shù)據(jù),非葉子節(jié)點就意味能夠存儲更多的key,樹的高度就越低,查詢的效率就越高
2.b樹,葉子和非葉子節(jié)點都存儲數(shù)據(jù),非葉子節(jié)點能夠存儲的數(shù)據(jù)就越少,樹的高度就越高,查詢的效率就越低
當節(jié)點存滿之后,就會對該節(jié)點進行分裂,然后增加到下一級節(jié)點存儲

B+Tree通過把data不放在非葉子節(jié)點來增加度(小節(jié)點),一般會一百個以上使得深度是3~5,從而減少查詢次數(shù)。并且,葉子節(jié)點之間會有指針,數(shù)據(jù)又是遞增的,這使得我們范圍查找可以通過指針連接查找,而不再從上面節(jié)點往下一個個找。

結(jié)論:B+Tree 既減少查詢次數(shù)又提供了很好的范圍查詢

InnoDB和MyISAM的區(qū)別

MyISAM引擎

mylsam索引文件和數(shù)據(jù)文件是分離的(非聚集索引:索引和data值是分開的),并且主鍵索引和輔助索引(二級索引)的存儲方式是一樣的

.frm文件是存儲:該表的數(shù)據(jù)表結(jié)構(gòu)相關(guān)信息
.MYD文件是存儲:該表的數(shù)據(jù)內(nèi)容
.MYI文件是存儲:該表的索引數(shù)據(jù)

引擎原理:

1.col1(15值)存儲的數(shù)據(jù),會以b+樹的格式進行存儲myd里面
2.當查詢某條數(shù)據(jù),會到葉子節(jié)點找到相應(yīng)索引值(15的索引值為0x07) 其實就是在myi里面找
3.在myi文件中找到索引值后,MyISAM引擎會根據(jù)索引值,到myd文件里面找到相應(yīng)位置的值

主鍵索引
聯(lián)合索引

InnoDB引擎

  • 數(shù)據(jù)文件本身就是索引文件
  • 表數(shù)據(jù)文件本身就是按B+Tree組織的一個索引結(jié)構(gòu)文件
  • 聚集索引-葉節(jié)點包含了完整的數(shù)據(jù)記錄

Innodb索引文件(聚集索引:索引和data值是在一起的),并且主鍵索引和二級索引儲存方式有所不同,如圖所示,二級索引的葉子節(jié)點不儲存數(shù)據(jù),僅儲存主鍵ID。

.frm文件是存儲:該表的數(shù)據(jù)表結(jié)構(gòu)相關(guān)信息
.ibd文件是存儲:該表的索引和數(shù)據(jù)是存在一起的

為什么建議InnoDB表必須建主鍵,并且推薦使用整形的自增主鍵

1.首先,為了滿足MySQL的索引數(shù)據(jù)結(jié)構(gòu)B+樹的特性,必須要有索引作為主鍵,可以有效提高查詢效率,因此InnoDB必須要有主鍵。如果不手動指定主鍵,InnoDB會從插入的數(shù)據(jù)中找出不重復(fù)的一列作為主鍵索引,如果沒找到不重復(fù)的一列,這時候InnoDB會選擇內(nèi)置的ROWID作為主鍵,寫入順序和ROWID增長順序一致;其次,索引的數(shù)據(jù)類型是整型,一方面整型占有的磁盤空間或內(nèi)存空間相比字符串更少,另一方面整型比較比字符串比較更快速,字符串比較是先轉(zhuǎn)換為ASCII碼,然后再進行比較的。
最后,B+樹本質(zhì)是多路多叉樹,如果主鍵索引不是自增的,那么后續(xù)插入的索引就會引起B(yǎng)+樹的其他節(jié)點的分裂和重新平衡,影響數(shù)據(jù)插入的效率,如果是自增主鍵,只用在尾節(jié)點做增加就可以。

2.為什么非主鍵索引結(jié)構(gòu)葉子節(jié)點存儲的是主鍵值?(一致性和節(jié)省存儲空間)
主鍵索引和非主鍵索引維護各自的B+樹結(jié)構(gòu),當插入的數(shù)據(jù)的時候,由于數(shù)據(jù)只有一份,通過非主鍵索引獲取到主鍵值,然后再去主鍵索引的B+樹數(shù)據(jù)結(jié)構(gòu)中找到對應(yīng)的行數(shù)據(jù),節(jié)省了內(nèi)存空間;
如果非主鍵索引的葉子節(jié)點也存儲一份數(shù)據(jù),如果通過非主鍵索引插入數(shù)據(jù),那么要向主鍵索引對應(yīng)的行數(shù)據(jù)進行同步,那么會帶來數(shù)據(jù)一致性問題??梢酝ㄟ^事務(wù)的方式解決,我們都知道使用事務(wù)后,就會對性能有所消耗。

聯(lián)合索引底層存儲結(jié)構(gòu)

定義聯(lián)合索引(員工級別,員工姓名,員工出生年月),將聯(lián)合索引按照索引順序放入節(jié)點中,新插入節(jié)點時,先按照聯(lián)合索引中的員工級別比較,如果相同會按照是員工姓名比較,如果員工級別和員工姓名都相同 最后是員工的出生年月比較。可以從圖中從上到下,從左到右看,第一個B+樹的節(jié)點 是通過聯(lián)合索引的員工級別比較的,第二個節(jié)點是 員工級別相同,會按照員工姓名比較,第三個節(jié)點是 員工級別和員工姓名都相同,會按照員工出生年月比較。

例如:

聯(lián)合索引:設(shè)置的順序是:emp_no,title,from_date這個三個字段

explain SELECT * from employee where emp_no = '10001' and title = 'xxx' and from_date = '2018-10-01 11:29:51'   ====會執(zhí)行聯(lián)合索引
explain SELECT * from employee where title = 'sss'           ====不會執(zhí)行聯(lián)合索引
explain SELECT * from employee where emp_no > '10003' and title = 'eeee'   ====不會執(zhí)行聯(lián)合索引

從上面的例子中可以知道,只會第一條才會執(zhí)行聯(lián)合索引,那是為什么呢
這就回到本文第一句話,索引是獲取數(shù)據(jù)排好序的數(shù)據(jù)結(jié)構(gòu),所以我們再做查詢的時候,肯定會先根據(jù)emp_no排序,再title排序,最后再from_date排序

例如:SELECT * from employee where title = 'sss' 底層都還沒有對emp_no排序呢,就直接查title了,是肯定不行的啦,因為并未排好序呢,所以不會走索引

由此即可得出最左前綴原理

使用聯(lián)合索引時,聯(lián)合索引定義的順序?qū)绊懙阶罱K查詢索引的使用情況和結(jié)果,例如定義了聯(lián)合索引(a,b,c) ,mysql會先從左邊的列優(yōu)先匹配,如果最左邊定義的列都沒有被使用到,在未使用覆蓋索引的情況下,mysql就會默認執(zhí)行全表掃描。

聯(lián)合索引底層數(shù)據(jù)結(jié)構(gòu)思考?
mysql會優(yōu)先以聯(lián)合索引的第一列開始匹配,此后才會匹配下一列,如果不指定第一列匹配的值,那么也就無法知道下一步要查詢那個節(jié)點(可以聯(lián)想B+樹的數(shù)據(jù)結(jié)構(gòu),第一列匹配到值后,會進行一次數(shù)據(jù)結(jié)構(gòu)的排序篩選,得出排好序的數(shù)據(jù)結(jié)構(gòu),在進行匹配下一列,得出最終結(jié)果,那么如果直接跳過第一列,匹配第二列,b+樹會無法找到排好序的數(shù)據(jù)結(jié)構(gòu)結(jié)果,就會進行全表掃描)
另外一種情況,如果遇到 ">"、"<"、"between"等這樣的范圍查詢,那么b+樹也就無法對下一列進行等值匹配了(可以聯(lián)想到,就算建立了索引,因為是范圍查詢,mysql會認為走索引會導(dǎo)致回表查詢過多,導(dǎo)致效率并不會比全表掃描快,最終mysql就會走全表掃描)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372