理解索引的特性
- 索引是幫助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)位置的值
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就會走全表掃描)