索引浅悉,類似于圖書(shū)目錄闽颇,可以根據(jù)目錄某個(gè)頁(yè)碼立即找到對(duì)應(yīng)的內(nèi)容。
索引的有點(diǎn)顯而易見(jiàn):(有可能)提升查詢效率
同時(shí)也有缺點(diǎn):增大數(shù)據(jù)庫(kù)空間占用雏掠,降低表更新的效率
從實(shí)現(xiàn)上來(lái)說(shuō)斩祭,索引可以分為兩種:聚集索引、非聚集索引乡话。
說(shuō)到索引的實(shí)現(xiàn)摧玫,不得不提B+樹(shù),提到B+樹(shù)绑青,就要先搞明白B樹(shù)诬像。
一、B樹(shù)與B+樹(shù)
1.B樹(shù)
B樹(shù)是一種多路平衡查找樹(shù)闸婴,一個(gè)高度為m的B樹(shù)有以下幾個(gè)特點(diǎn):
1.根節(jié)點(diǎn)至少有兩個(gè)子節(jié)點(diǎn)坏挠;
2.每個(gè)中間節(jié)點(diǎn)都包含k-1個(gè)元素和k個(gè)子節(jié)點(diǎn),其中m/2 <= k <= m掠拳;
3.每個(gè)葉節(jié)點(diǎn)包含k-1個(gè)元素癞揉,其中m/2 <= k <= m;
4.所有的葉節(jié)點(diǎn)都位于同一層溺欧;
5.每個(gè)節(jié)點(diǎn)中的元素從小到大排列喊熟,節(jié)點(diǎn)中k-1個(gè)元素正好是k個(gè)子節(jié)點(diǎn)所包含元素的值域劃分。
查找
MySQL將數(shù)據(jù)按照頁(yè)來(lái)存儲(chǔ)姐刁,默認(rèn)一頁(yè)為16KB芥牌,查詢時(shí),不會(huì)只加載某一條數(shù)據(jù)聂使,而是將目標(biāo)所在的頁(yè)都加載到PageCache中壁拉。B樹(shù)中,一個(gè)節(jié)點(diǎn)代表了一頁(yè)柏靶,查找的時(shí)候弃理,每經(jīng)過(guò)一個(gè)節(jié)點(diǎn)就需要一次磁盤(pán)IO,可以發(fā)現(xiàn)屎蜓,以上圖中的B樹(shù)為例痘昌,查找的最壞情況需要3次磁盤(pán)IO,即樹(shù)的高度炬转。
插入
B樹(shù)還有一個(gè)特點(diǎn)辆苔,自平衡,這也使得其在插入新數(shù)據(jù)的時(shí)候比較復(fù)雜扼劈。還是用上圖的B樹(shù)舉例驻啤,假設(shè)我們要插入一個(gè)新值4。自頂向下查找到應(yīng)該插入的位置荐吵,發(fā)現(xiàn)應(yīng)該在3,5之間骑冗。
節(jié)點(diǎn)3赊瞬,5已經(jīng)是兩元素節(jié)點(diǎn),無(wú)法再增加沐旨。父親節(jié)點(diǎn) 2森逮, 6 也是兩元素節(jié)點(diǎn),也無(wú)法再增加磁携。根節(jié)點(diǎn)9是單元素節(jié)點(diǎn),可以升級(jí)為兩元素節(jié)點(diǎn)良风。于是拆分節(jié)點(diǎn)3谊迄,5與節(jié)點(diǎn)2,6烟央,讓根節(jié)點(diǎn)9升級(jí)為兩元素節(jié)點(diǎn)4统诺,9。節(jié)點(diǎn)6獨(dú)立為根節(jié)點(diǎn)的第二個(gè)孩子疑俭。
刪除
再來(lái)看看刪除粮呢,假設(shè)我們要?jiǎng)h除11這個(gè)數(shù)據(jù)。
刪除11后钞艇,節(jié)點(diǎn)12只有一個(gè)孩子啄寡,不符合B樹(shù)規(guī)范。因此找出12,13,15三個(gè)節(jié)點(diǎn)的中位數(shù)13哩照,取代節(jié)點(diǎn)12挺物,而節(jié)點(diǎn)12自身下移成為第一個(gè)孩子。(這個(gè)過(guò)程稱為左旋)
B樹(shù)主要應(yīng)用于文件系統(tǒng)及某些數(shù)據(jù)庫(kù)索引飘弧,比如MongoDB识藤。對(duì)于同等數(shù)量的數(shù)據(jù),相較于二叉查找樹(shù)次伶,B樹(shù)的每一層能存放更多數(shù)據(jù)痴昧,二叉樹(shù)比較“瘦高”,而B(niǎo)樹(shù)比較“胖矮”冠王,這樣能有效減少查詢過(guò)程中磁盤(pán)IO的次數(shù)赶撰。
2.B+樹(shù)
B+樹(shù)在B樹(shù)的基礎(chǔ)上做了改進(jìn)。先來(lái)看看B+樹(shù)的特征:
1.有k個(gè)子樹(shù)的中間節(jié)點(diǎn)包含有k個(gè)元素(B樹(shù)中是k-1個(gè)元素)版确,每個(gè)元素不保存數(shù)據(jù)扣囊,只用來(lái)索引,所有數(shù)據(jù)都保存在葉子節(jié)點(diǎn)绒疗。
2.所有的葉子結(jié)點(diǎn)中包含了全部元素的信息侵歇,及指向含這些元素記錄的指針,且葉子結(jié)點(diǎn)本身依關(guān)鍵字的大小自小而大順序鏈接吓蘑。
3.所有的中間節(jié)點(diǎn)元素都同時(shí)存在于子節(jié)點(diǎn)惕虑,在子節(jié)點(diǎn)元素中是最大(或最蟹爻濉)元素。
圖中的B+樹(shù)溃蔫,父節(jié)點(diǎn)中的元素是各子節(jié)點(diǎn)中的最大元素健提。可以發(fā)現(xiàn)伟叛,根節(jié)點(diǎn)的最大值15私痹,也是整個(gè)樹(shù)的最大值;此外统刮,所有的葉節(jié)點(diǎn)包含了全量元素紊遵,每個(gè)葉節(jié)點(diǎn)都有一個(gè)指針指向下一個(gè)葉節(jié)點(diǎn),形成了一個(gè)有序鏈表侥蒙。
除此之外暗膜,還需要了解一個(gè)概念——衛(wèi)星數(shù)據(jù)(Satellite Data)。所謂衛(wèi)星數(shù)據(jù)鞭衩,指的是索引元素所指向的數(shù)據(jù)記錄学搜,比如數(shù)據(jù)庫(kù)中的某一行。B樹(shù)中论衍,無(wú)論是中間節(jié)點(diǎn)還是葉節(jié)點(diǎn)瑞佩,都帶有衛(wèi)星數(shù)據(jù)。這個(gè)特點(diǎn)會(huì)導(dǎo)致中間節(jié)點(diǎn)不能存儲(chǔ)大量的索引饲齐。
而B(niǎo)+樹(shù)針對(duì)這個(gè)做了優(yōu)化钉凌,只有葉節(jié)點(diǎn)帶有衛(wèi)星數(shù)據(jù),中間節(jié)點(diǎn)只包含索引元素和指針捂人。
我們假設(shè)一個(gè)非頁(yè)子節(jié)點(diǎn)是 16kb御雕,每個(gè)索引,即主鍵是 bigint滥搭,即 8b酸纲,指針為 8b。那么每頁(yè)能存儲(chǔ)大約 1000 個(gè)索引(16KB / (8B + 8B))瑟匆。
而一顆 3 層的 B+樹(shù)能夠存儲(chǔ)多少索引呢闽坡?如下圖:
大約能夠存儲(chǔ) 10 億個(gè)索引。由此可見(jiàn)愁溜,相比于B樹(shù)疾嗅,B+樹(shù)更加矮胖,查詢的時(shí)候(平均情況下)磁盤(pán)IO的次數(shù)更少冕象。
除此之外代承,范圍查詢時(shí),B樹(shù)找到范圍起始的索引元素后渐扮,只能通過(guò)中序遍歷的方式论悴,過(guò)程復(fù)雜掖棉;而B(niǎo)+樹(shù)由于葉節(jié)點(diǎn)全量的元素形成了一個(gè)鏈表,方便快捷膀估。
相比較于B樹(shù)幔亥,B+樹(shù)實(shí)現(xiàn)索引有以下三個(gè)特點(diǎn):1.更少的磁盤(pán)IO;2.查詢性能穩(wěn)定(因?yàn)镮O次數(shù)總等于樹(shù)高)察纯;3.范圍查詢簡(jiǎn)便帕棉。
二、索引分類
1.單列索引 與 復(fù)合索引
只包含一個(gè)字段的索引叫做單列索引捐寥,包含兩個(gè)或以上字段的索引叫做復(fù)合索引(或組合索引)笤昨。建立復(fù)合索引時(shí),字段的順序極其重要握恳。
下面這個(gè)SQL語(yǔ)句在 列X,列Y捺僻,列Z 上建立了一個(gè)復(fù)合索引乡洼。
CREATE INDEX 索引名 ON 表名(列名X, 列名Y, 列名Z);
其實(shí)這相當(dāng)于建立了三個(gè)索引,分別是:
1匕坯、單列索引(列X) 2束昵、復(fù)合索引(列X, 列Y) 3、復(fù)合索引(列X葛峻,列Y锹雏,列Z)。
2.唯一索引 與 主鍵
唯一索引是在表上一個(gè)或者多個(gè)字段組合建立的索引术奖,這個(gè)(或這幾個(gè))字段的值組合起來(lái)在表中不可以重復(fù)礁遵。一張表可以建立任意多個(gè)唯一索引,但一般只建立一個(gè)采记。
主鍵是一種特殊的唯一索引佣耐,區(qū)別在于,唯一索引列允許null值唧龄,而主鍵列不允許為null值兼砖。一張表最多建立一個(gè)主鍵,也可以不建立主鍵既棺。
3.聚簇索引讽挟、非聚簇索引
聚簇索引的葉子節(jié)點(diǎn)就是數(shù)據(jù)節(jié)點(diǎn),而非聚簇索引的葉子節(jié)點(diǎn)仍然是索引節(jié)點(diǎn)丸冕,只不過(guò)有指向?qū)?yīng)數(shù)據(jù)塊的指針耽梅。通常我們建表的時(shí)候,會(huì)指定一個(gè)字段為主鍵晨仑,主鍵唯一且不能為空褐墅,一般情況下主鍵就是聚簇索引拆檬。一張表只允許存在一個(gè)聚簇索引,因?yàn)檎鎸?shí)數(shù)據(jù)的物理順序只能有一種妥凳。如果一張表上還沒(méi)有聚簇索引竟贯,為它新創(chuàng)建聚簇索引時(shí),就需要對(duì)已有數(shù)據(jù)重新進(jìn)行排序逝钥,所以對(duì)表進(jìn)行修改速度較慢是聚簇索引的缺點(diǎn)屑那,對(duì)于經(jīng)常更新的列不宜建立聚簇索引。
我們總說(shuō)的數(shù)據(jù)庫(kù)調(diào)優(yōu)的手段之一——建索引艘款,通常指的是非聚簇索引持际。一張表可以建立任意多個(gè)索引,每個(gè)索引可以是任意多個(gè)字段的組合哗咆。索引可能會(huì)提高查詢速度(如果查詢時(shí)使用了索引)蜘欲,但一定會(huì)減慢寫(xiě)入速度,因?yàn)槊看螌?xiě)入時(shí)都需要更新索引晌柬,所以索引只應(yīng)該加在經(jīng)常需要搜索的列上姥份,不要加在寫(xiě)多讀少的列上。
執(zhí)行某條查詢SQL年碘,從非聚簇索引到聚簇索引澈歉,再到數(shù)據(jù)節(jié)點(diǎn),流程如下:
不管以任何方式查詢表屿衅, 最終都會(huì)利用主鍵通過(guò)聚簇索引來(lái)定位到數(shù)據(jù)埃难, 聚簇索引(主鍵)是通往真實(shí)數(shù)據(jù)所在的唯一路徑。然而凡事都有例外涤久,有一種非主流的方式可以不通過(guò)聚簇索引就能查詢出所需要的數(shù)據(jù)涡尘,這就是覆蓋索引。當(dāng)為字段建立索引以后拴竹, 字段中的內(nèi)容會(huì)被同步到索引之中悟衩, 如果為一個(gè)索引指定兩個(gè)字段, 那么這個(gè)兩個(gè)字段的內(nèi)容都會(huì)被同步至索引之中栓拜。
先看下面這個(gè)SQL語(yǔ)句
-- 建立索引
create index index_birthday_and_user_name on user_info(birthday, user_name);
-- 查詢生日在1991年11月1日出生用戶的用戶名
select user_name from user_info where birthday = '1991-11-1'
這句SQL語(yǔ)句的執(zhí)行過(guò)程如下:
通過(guò)非聚集索引index_birthday_and_user_name查找birthday等于1991-11-1的葉節(jié)點(diǎn)的內(nèi)容座泳,然而, 葉節(jié)點(diǎn)中除了有user_name表主鍵ID的值以外幕与, user_name字段的值也在里面挑势, 因此不需要通過(guò)主鍵ID值的查找數(shù)據(jù)行的真實(shí)所在, 直接取得葉節(jié)點(diǎn)中user_name的值返回即可啦鸣。 通過(guò)這種覆蓋索引直接查找的方式潮饱, 可以省略不使用覆蓋索引查找的后面兩個(gè)步驟, 大大的提高了查詢性能诫给。
4.其他
其他索引的類型還有外鍵索引香拉、全文索引等啦扬,這里簡(jiǎn)單帶過(guò),不再展開(kāi)來(lái)講凫碌。
外鍵索引:只有InnoDB類型的表才可以使用外鍵索引享扔,保證數(shù)據(jù)的一致性捏雌、完整性和實(shí)現(xiàn)級(jí)聯(lián)操作岩灭。
全文索引:MySQL 自帶的全文索引只能用于 InnoDB雀摘、MyISAM ,并且只能對(duì)英文進(jìn)行全文檢索苦掘,一般使用全文索引引擎(ES换帜,Solr)。