B+樹索引的使用

閱讀該部分內(nèi)容時(shí)蛀恩,需要提前了解B+Tree樹基本知識(shí)點(diǎn)领迈,否則可能有些內(nèi)容你并不能很好的體會(huì)到算吩。對(duì)于下面幾點(diǎn)內(nèi)容如果不是很清楚拓萌,可以閱讀我之前寫的Mysql簡(jiǎn)敘一文中的內(nèi)容進(jìn)行了解岁钓。

  • 每個(gè)索引都是一顆B+Tree,最下面的一層是葉子節(jié)點(diǎn),其余都是內(nèi)節(jié)點(diǎn)屡限。而葉子節(jié)點(diǎn)中存儲(chǔ)的都是用戶相關(guān)數(shù)據(jù)品嚣,而內(nèi)節(jié)點(diǎn)存儲(chǔ)的是頁(yè)節(jié)點(diǎn)相關(guān)信息。
  • InnoDB會(huì)自動(dòng)為主鍵(沒有會(huì)自動(dòng)添加)建立聚族索引钧大,聚族索引包含了所有的用戶數(shù)據(jù)翰撑。
  • 二級(jí)索引中的用戶數(shù)據(jù)是通過索引列和主鍵組成的,利用二級(jí)索引找到對(duì)應(yīng)的記錄時(shí)拓型,如果需要獲取整條記錄信息需要通過二級(jí)索引中的主鍵通過回表的方式從聚族索引中獲取额嘿。
  • B+Tree中每層節(jié)點(diǎn)都是按照索引列從小到大的順序排列的瘸恼。
  • 查找數(shù)據(jù)時(shí)是從B+Tree的根節(jié)點(diǎn)開始往下找劣挫。

索引使用示例

對(duì)于后面的示例,我們定義如下表結(jié)構(gòu):

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶ID',
  `user_name` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '用戶姓名',
  `age` int(11) NOT NULL COMMENT '年齡',
  `phone_no` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '手機(jī)號(hào)',
  PRIMARY KEY (`id`),
  KEY `index_user_age` (`user_name`,`age`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

從表結(jié)構(gòu)中我們可以知道东帅,在這個(gè)表中我們存在兩個(gè)索引压固,一個(gè)是根據(jù)主鍵和用戶數(shù)據(jù)創(chuàng)建的聚族索引,另外一個(gè)則是user_name和age組成的二級(jí)索引靠闭,所以在這個(gè)表中存在兩顆B+Tree樹帐我。

全值匹配

SELECT  * FROM t_user WHERE user_name = 'tom' AND age = 18;

我們建立的index_user_age索引兩個(gè)字段都在最后的查詢條件中,這個(gè)查詢的執(zhí)行過程如下:

  • 首先通過index_user_age這個(gè)B+Tree樹能很快的找到對(duì)應(yīng)的記錄愧膀。
  • 因?yàn)樾枰祷厮袛?shù)據(jù)拦键,而index_user_age該索引樹中只有user_name,age檩淋,id信息芬为,所以需要通過回表的方式從聚族索引找到對(duì)應(yīng)的整條用戶數(shù)據(jù)然后返回。

可能你一直有一個(gè)疑問就是我們的查詢語句中條件的順序是user_namge和age蟀悦,這個(gè)順序和索引中字段的順序一樣能用到索引媚朦。但是如果我的查詢語句中條件的順序是age和user_name時(shí),還能用到索引嗎日戈?答案是能用到索引询张。在Mysql中存在查詢優(yōu)化引擎,它會(huì)根據(jù)你創(chuàng)建的索引里的字段順序去優(yōu)化你的SQL浙炼,所以對(duì)于這一點(diǎn)完全不用擔(dān)心份氧。

查詢分析結(jié)果.png

從上面的分析結(jié)果可以看出它們的結(jié)果是一樣的,所以你是不用擔(dān)心查詢條件中的字段順序的弯屈。

最左匹配原則

SELECT * FROM t_user WHERE age =18;

上面的SQL能用到索引嗎半火?如果你了解B+Tree索引知識(shí),你就能知道是不行的季俩。因?yàn)樗环献钭笃ヅ湓瓌t钮糖,那為什么會(huì)這樣子呢?我們先看看index_user_age索引具體是什么樣子的。

index_user_age索引結(jié)構(gòu).png

通過上圖我想你很清楚索引的結(jié)構(gòu)了店归。最后一層就是我們的索引數(shù)據(jù)阎抒,它們的順序是按照user_name->age排列的,我們?cè)谑褂胕ndex_user_age索引時(shí)消痛,我們先只能先按照user_name按順序找到所有的對(duì)應(yīng)的索引數(shù)據(jù)且叁,然后再根據(jù)age去做進(jìn)一步的篩選,所以對(duì)于上面的SQL語句我們無法用到index_user_age索引秩伞。那下面這個(gè)語句可以用到index_user_age索引嗎逞带?

SELECT * FROM t_user WHERE user_name = 'tom';

相信你心中已經(jīng)有了答案。我們直接看這個(gè)語句的分析結(jié)果纱新,看看是不是和我們說的一致展氓。

最左匹配原則驗(yàn)證.png

從explain結(jié)果可以看出,第一條SQL是無法用到任何索引的脸爱,但是第二條可以用到index_user_age索引遇汞。

索引下推

我們修改index_user_age索引,在原先index_user_age的基礎(chǔ)上增加phone_no索引字段簿废,創(chuàng)建新的索引index_user_age_phone索引空入。修改后的表結(jié)構(gòu)如下:

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶ID',
  `user_name` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '用戶姓名',
  `age` int(11) NOT NULL COMMENT '年齡',
  `phone_no` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '手機(jī)號(hào)',
  `address` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_user_age_phone` (`user_name`,`age`,`phone_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

現(xiàn)在我有一個(gè)查詢?nèi)缦拢?/p>

SELECT * FROM t_user WHERE user_name = 'jack' AND phone_no = '11111111111';

我現(xiàn)在想問的是這個(gè)查詢中能用到索引嗎?phone_no這個(gè)能用到索引嗎族檬?根據(jù)前面的最左匹配原則可以知道這個(gè)查詢能用到索引index_user_age_phone歪赢,好像phone_no這個(gè)是沒法用到索引的。對(duì)于index_user_age_phone索引來說单料,它內(nèi)部索引列的字段順序是按照user_name->age->phone_no按順序排列的埋凯,對(duì)于我們的查詢條件,搜索時(shí)我們只能用到user_name這個(gè)列了看尼。但是實(shí)際上phone_no這個(gè)列也能用到递鹉,只不過不是用在搜索查詢中。
通過二級(jí)索引查詢記錄時(shí)藏斩,我們只能獲取到索引列上的值和主鍵躏结,如果我們的返回結(jié)果中需要返回該記錄的所有字段信息,我們就不得不通過回表的方式從聚族索引再次獲取該記錄的完整信息狰域。這個(gè)過程如下所示:

索引下推.png

首先我們根據(jù)user_name找到所有滿足記錄數(shù)據(jù)媳拴,然后通過回表的方式從對(duì)應(yīng)的主鍵索引(聚族索引)中找到完整的用戶數(shù)據(jù),然后再篩選phone_no這個(gè)條件兆览∏龋回表這個(gè)操作你可以理解為通過主鍵ID從聚族索引中獲取用戶數(shù)據(jù),索引回表是有消耗的抬探,雖然通過主鍵查找記錄很快子巾,但是通過user_name匹配到的數(shù)據(jù)很多時(shí)帆赢,還是會(huì)影響查詢速度的。那有沒有方式可以減少回表的次數(shù)呢线梗?
估計(jì)聰明的你肯定想到了椰于,從圖中我們可以看到現(xiàn)在有三條記錄滿足 user_name=jack 這個(gè)條件,正常情況下我們需要回表三次仪搔。如果我在回表前就篩選phone_no這個(gè)查詢條件瘾婿,這樣是不是就能減少我們的回表次數(shù)呢?如果在回表前判斷phone_no這個(gè)條件烤咧,這時(shí)我們只有一條記錄滿足條件偏陪,回表次數(shù)也從3次變成1次了。
對(duì)于上面這種方式煮嫌,我們就叫做 索引下推 笛谦。這個(gè)特性是在Mysql5.6及以后才版本才提供,對(duì)于之前的Mysql版本是沒有這個(gè)優(yōu)化的立膛。

列前綴匹配

在開發(fā)中我們可能會(huì)聽別人說like查詢無法使用索引揪罕,但是實(shí)際真的如此嗎梯码?其實(shí)這個(gè)跟我們之前說的最左匹配原則很相似宝泵,例如有下面的這個(gè)查詢:

SELECT * FROM t_user WHERE user_name LIKE 't%';

這個(gè)查詢的意思是查找名字以t開頭的用戶,對(duì)于這個(gè)查詢是能使用到索引的轩娶。在這個(gè)查詢中會(huì)用到index_user_age_phone這個(gè)索引儿奶,因?yàn)樵诙?jí)索引樹中,索引的內(nèi)容是按照user_name鳄抒、age闯捎、phone_no這個(gè)順序排列的。而我們的查詢是查user_name以字母t開頭的许溅,所以在這個(gè)查詢中我們的索引能生效瓤鼻,這個(gè)方式我們通常稱作列前綴匹配。但是下面這個(gè)SQL索引是不生效的贤重。

SELECT * FROM t_user WHERE user_name LIKE '%t%';

這個(gè)SQL的意思是查找名字中包含字母t的用戶茬祷,所以這個(gè)時(shí)候索引不會(huì)生效。我們可以通過explain結(jié)果來驗(yàn)證我們的結(jié)論并蝗。

列前綴匹配.png

從上面的explain結(jié)果我們可以看出祭犯,對(duì)于like 't%' 即前綴匹配是可以用到索引的,而后面的 like '%t%' 是無法用到索引的滚停。

索引用于排序

索引不僅僅只是用作查詢條件沃粗,同時(shí)索引還能作用于排序。例如現(xiàn)在有下面這個(gè)排序:

SELECT * FROM t_user ORDER BY user_name,age,phone_no LIMIT 10;

例如上面這個(gè)SQL键畴,它是可以用到索引的最盅。因?yàn)閷?duì)于index_user_age_phone這個(gè)索引樹,它里面的數(shù)據(jù)是按照user_name、age和phone_no組合的值從小到大排列的涡贱,所以這里是能用到索引的挂签。可能你會(huì)問盼产,默認(rèn)情況下排序是ASC饵婆,跟索引里面一樣是從小到大,但如果換成DESC戏售,即從大到小的順序排列那還能使用索引嗎侨核?答案是可以,只要字段順序沒變灌灾,不管是ASC還是DESC它都能用到索引搓译。(注意:如果使用explain查看結(jié)果可能發(fā)現(xiàn)它沒有使用任何索引,這可能是因?yàn)槟惚碇械臄?shù)據(jù)量太少锋喜,Mysql掃全表比使用索引更快些己,因?yàn)槭褂枚?jí)索引需要回表)。
那是不是排序都能用上索引呢嘿般?答案是不是的段标。并不是所有的排序都能用上索引,對(duì)于下面的幾種情況是無法通過索引來排序的炉奴。

  • ASC和DESC混用
SELECT * FROM t_user ORDER BY user_name ASC ,age DESC ,phone_no ASC LIMIT 10;
  • where子句中出現(xiàn)非排序的索引列
SELECT * FROM t_user WHERE address = 'xxxx' ORDER BY user_name LIMIT 10;
  • 排序列包含非統(tǒng)一個(gè)索引的列
SELECT * FROM t_user ORDER BY user_name,address;
  • 排序列使用了函數(shù)
SELECT * FROM t_user ORDER BY UPPER(user_name)

索引用于分組

索引用于分組這個(gè)跟排序類似逼庞,基本上的原則也都一樣。

回表和索引覆蓋

前面說過瞻赶,有時(shí)候使用explain分析語句時(shí)發(fā)現(xiàn)并不是按照一些規(guī)則來的急前,至于為什么會(huì)如此這個(gè)就跟回表有關(guān)了叹螟。我們?cè)谑褂枚?jí)索引找到對(duì)應(yīng)數(shù)據(jù)后赶诊,如果我們要返回的列不在索引中赤屋,這個(gè)時(shí)候我們就需要進(jìn)行回表∈σ荩回表它需要通過ID再去聚族索引中找到原始數(shù)據(jù)司倚,并且這里面并不是一個(gè)順序I/O。如果對(duì)于大量的數(shù)據(jù)需要回表時(shí)字旭,Mysql往往有時(shí)候不會(huì)使用二級(jí)索引而是直接掃表对湃。因?yàn)榇罅康幕乇硭男视锌赡苓€沒有掃全表的效率高。
對(duì)于我們查詢而言遗淳,如果應(yīng)該盡量不使用號(hào)返回所有字段拍柒,而應(yīng)該只取我們需要的字段返回即可。同時(shí)我們還可以通過索引覆蓋*的方式來減少回表屈暗。例如我有一個(gè)查詢是通過user_name查詢用戶的年齡和手機(jī)號(hào)拆讯,我在創(chuàng)建索引時(shí)不僅僅只在user_name列上創(chuàng)建索引脂男,我可以創(chuàng)建一個(gè)由user_name、age和phone_no三個(gè)字段組成的索引种呐,而查詢的返回值只需要返回這三個(gè)字段即可宰翅。

建索引的一些建議

對(duì)于索引而言,并不是越多越好爽室。索引是可以提高我們的查詢效率汁讼,但是如果索引過多也會(huì)帶來一些負(fù)面影響。每個(gè)索引在InnoDB中都是一顆B+Tree阔墩,多一個(gè)索引就多一顆B+Tree嘿架,這會(huì)占用過多的磁盤空間。同時(shí)在更新和刪除時(shí)啸箫,需要同時(shí)更新和刪除索引中的數(shù)據(jù)耸彪,這樣會(huì)導(dǎo)致數(shù)據(jù)庫(kù)的寫入性能受影響。很多情況下面忘苛,有些索引是多余的蝉娜,我們應(yīng)該精簡(jiǎn)我們的索引。通常我們?cè)诮ㄋ饕龝r(shí)應(yīng)該有下面的幾個(gè)原則:

  1. 只為用戶搜索扎唾、排序或分組的列上建立索引召川。
    對(duì)于這點(diǎn)我想很好理解,如果有些列我們根本用不上那就沒必要建索引稽屏,多建的索引并不能給我們帶來好處反而還浪費(fèi)了服務(wù)器的資源扮宠。
  2. 為基數(shù)大的列建立索引西乖。
    什么是基數(shù)呢狐榔?例如性別這個(gè)列,正常情況下我們一般也就是存三個(gè)值获雕。例如:男薄腻、女、未知届案。即使我們的表里面存了100萬條數(shù)據(jù)庵楷,它的值還是在這三個(gè)里面,而這個(gè)列的基數(shù)就是3楣颠。為了加快搜索我們?cè)谛詣e這個(gè)列上面創(chuàng)建索引尽纽,這種方式并不能帶來查詢性能上的明顯改善,對(duì)于這種列我們就沒有必要在這個(gè)列上單獨(dú)創(chuàng)建索引了童漩。
  3. 索引列的類型盡量小弄贿。
    例如對(duì)于整數(shù)類型在Mysql中就有好幾種,例如tinyint,mediumint,int,bigint矫膨。它們所占的空間依次遞增差凹,而能表示的數(shù)字大小也同樣依次遞增期奔。如果一個(gè)整數(shù)列的值能用int表示完整我們就不應(yīng)該用bigint,數(shù)據(jù)類型越小危尿,索引所占的空間也就越小呐萌,那么在一個(gè)業(yè)內(nèi)能存放的數(shù)據(jù)也就越多。這樣就能減少磁盤I/O的次數(shù)谊娇,減少磁盤I/O的次數(shù)也就意味著性能的提升肺孤。
  4. 刪除冗余和重復(fù)索引
    什么樣的索引算是冗余和重復(fù)索引呢?例如我創(chuàng)建了一個(gè)以u(píng)ser_name列和age列組合而成的索引index_user_age济欢,然后又創(chuàng)建了一個(gè)以u(píng)ser_name列的索引index_user渠旁,這種情況下索引就重復(fù)了。對(duì)于這種情況船逮,我們可以刪掉index_user索引顾腊,因?yàn)閕ndex_user_age索引完全可以替代index_user索引的效果,所以我們沒必要多加一個(gè)索引浪費(fèi)服務(wù)器資源挖胃。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杂靶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子酱鸭,更是在濱河造成了極大的恐慌吗垮,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凹髓,死亡現(xiàn)場(chǎng)離奇詭異烁登,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蔚舀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門饵沧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赌躺,你說我怎么就攤上這事狼牺。” “怎么了礼患?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵是钥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我缅叠,道長(zhǎng)悄泥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任肤粱,我火速辦了婚禮弹囚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狼犯。我一直安慰自己余寥,他們只是感情好领铐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宋舷,像睡著了一般绪撵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上祝蝠,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天音诈,我揣著相機(jī)與錄音,去河邊找鬼绎狭。 笑死细溅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的儡嘶。 我是一名探鬼主播喇聊,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蹦狂!你這毒婦竟也來了誓篱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤凯楔,失蹤者是張志新(化名)和其女友劉穎窜骄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摆屯,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邻遏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虐骑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片准验。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖富弦,靈堂內(nèi)的尸體忽然破棺而出沟娱,到底是詐尸還是另有隱情,我是刑警寧澤腕柜,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站矫废,受9級(jí)特大地震影響盏缤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蓖扑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一唉铜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧律杠,春花似錦潭流、人聲如沸竞惋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拆宛。三九已至,卻和暖如春讼撒,著一層夾襖步出監(jiān)牢的瞬間浑厚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工根盒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钳幅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓炎滞,卻偏偏與公主長(zhǎng)得像敢艰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子册赛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353