MySQL 的索引長什么樣子狞膘?索引到底是怎么加速查詢的研乒?
事實(shí)上,在你還沒有執(zhí)行?create index?語句的時候佳窑,MySQL 就已經(jīng)創(chuàng)建索引了制恍。
聚簇索引
執(zhí)行建表語句:
CREATE TABLE `student` (
`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT '主鍵id',
`student_no` VARCHAR ( 64 ) COMMENT '學(xué)號',
`name` VARCHAR ( 64 ) COMMENT '學(xué)生姓名',
`age` INT COMMENT '學(xué)生年齡',
PRIMARY KEY ( `id` )) ENGINE = INNODB CHARSET = utf8mb4 COMMENT = '學(xué)生信息表';
插入 5 條數(shù)據(jù):
INSERT INTO student ( student_no, NAME, age )
VALUES
( 101, "Alice", 18 ),
( 102, "Bob", 19 ),
( 104, "Brandt", 15 ),
( 105, "David", 19 ),
( 109, "David", 18 );
在插入的過程中,MySQL 會用你指定的主鍵神凑,在這里是遞增主鍵净神,維護(hù)起一棵 B+樹,主鍵從 1 開始遞增耙厚,插入五條强挫,所以是 1 到 5:
從 1 到 5,一個一個插入薛躬,你會看到 B+樹在插入的過程中是怎么維護(hù)它的幾個特性的:
有序:左邊節(jié)點(diǎn)比右邊小
自平衡:左右兩邊數(shù)量趨于相等
節(jié)點(diǎn)分裂:節(jié)點(diǎn)在遇到元素數(shù)量超過節(jié)點(diǎn)容量時俯渤,是如何分裂成兩個的,這個也是 MySQL 頁分裂的原理
模擬工具只支持插入一個值型宝,所以你看不到主鍵之外的其他數(shù)據(jù)八匠,實(shí)際上,這棵 B+樹的葉子節(jié)點(diǎn)是帶有行的全部數(shù)據(jù)的:
如果沒有這棵 B+樹趴酣,你要根據(jù)主鍵查詢梨树,比如
select*fromstudentwhereid=5;
數(shù)據(jù)是無序的,你只能全表掃描
有同學(xué)會說主鍵不是遞增的嗎岖寞,那不就可以用二分法來查找抡四?不是的,主鍵雖然是遞增的仗谆,但是如果你寫入磁盤時指巡,沒有去維護(hù)有序數(shù)組這樣一個數(shù)據(jù)結(jié)構(gòu)(比如你刪掉了 4,怎么把 5 往前面挪)隶垮,那數(shù)據(jù)在磁盤里依舊是無序的藻雪,查找時只能隨機(jī)查找,而如果你維護(hù)了有序數(shù)組這樣的數(shù)據(jù)結(jié)構(gòu)狸吞,其實(shí)也是建了索引勉耀,只是建了不一樣的數(shù)據(jù)結(jié)構(gòu)的索引罷了。
現(xiàn)在有了這棵 B+樹蹋偏,數(shù)據(jù)被有規(guī)律的存儲起來便斥,查找id=5,也不再大浪淘沙威始,而是變得很有章法:
從上到下椭住,先找到 3,5 比它大字逗,找右節(jié)點(diǎn)
接著找到 4京郑,發(fā)現(xiàn) 5 還是比它大宅广,繼續(xù)找右節(jié)點(diǎn)
這次到達(dá)葉子節(jié)點(diǎn)了,葉子節(jié)點(diǎn)是一個遞增的數(shù)組些举,那就用二分法跟狱,找到?id=5?的數(shù)據(jù)
你要訪問磁盤的次數(shù),是由這棵樹的層數(shù)決定的户魏。為了方便說明驶臊,我在文章里舉的例子的數(shù)據(jù)量不會太大,所以用不用索引叼丑,性能提升的效果不明顯关翎,但是你可以腦補(bǔ)下大數(shù)據(jù)量的畫面。
如果你沒有指定主鍵呢鸠信?沒關(guān)系纵寝,唯一鍵也可以。
連唯一鍵也沒有星立?也沒關(guān)系爽茴,mysql會給你建一個rowid字段,用它來組織這棵 B+樹.
反正 MySQL 就一個目的绰垂,數(shù)據(jù)要有規(guī)律的存儲起來室奏,數(shù)據(jù)是否被規(guī)律的管理起來,是數(shù)據(jù)庫和文件系統(tǒng)區(qū)分開來的重要因素劲装。
這個 MySQL 無論如何都會建起來胧沫,并且存儲有完整行數(shù)據(jù)的索引,就叫聚簇索引(clustered index)占业。
二級索引
聚簇索引只能幫你加快主鍵查詢绒怨,但是如果你想根據(jù)姓名查詢呢?
對不起纺酸,看看上面這棵樹你就知道窖逗,數(shù)據(jù)并沒有按照姓名進(jìn)行組織址否,所以餐蔬,你還是只能全表掃描。
不想全表掃描佑附,怎么辦樊诺?那就給姓名字段也加個索引,讓數(shù)據(jù)按照姓名有規(guī)律的進(jìn)行組織:
create index idx_name onstudent(name);
這時候 MySQL 又會建一棵新的 B+樹:
你會發(fā)現(xiàn)這棵樹的葉子節(jié)點(diǎn)音同,只有姓名和主鍵ID兩個字段词爬,沒有行的完整數(shù)據(jù),這時候你執(zhí)行:
select*fromstudentwherename="David";
MySQL 到你剛剛創(chuàng)建的這棵 B+樹 查詢权均,快速查到有兩條姓名是“David”的記錄顿膨,并且拿到它們的主鍵锅锨,分別是 4 和 5,但是你要的是select *呀恋沃,怎么辦必搞?
別忘了,MySQL 在一開始就給你建了一棵 B+樹 了囊咏,把這兩棵樹恕洲,放在一起,拿著從這棵樹上查到的兩個主鍵ID梅割,去聚簇索引找霜第,事情不就解決了?
這個不帶行數(shù)據(jù)完整信息的索引户辞,就叫二級索引(secondary index)泌类,也叫輔助索引。
復(fù)合索引
繼續(xù)咆课,如果我還想根據(jù)姓名和年齡同時查詢呢末誓?
select*fromstudentwherename="David"and age=18;
還是那個道理,數(shù)據(jù)雖然按照 name 有規(guī)律的組織了书蚪,但是沒有按照 age 有規(guī)律組織喇澡,所以我們要給name和age同時建索引:
create index idx_name_age onstudent(name,age);
這時候 MySQL 又會建一棵 B+樹,這下 B+樹 的節(jié)點(diǎn)里面殊校,不只有 name晴玖,還有 age 了:
注意觀察我用紅色虛線框出來的那兩個節(jié)點(diǎn),這是這棵樹和上面那棵只給 name 建索引的樹的唯一區(qū)別为流,兩個元素?fù)Q了個位呕屎,因?yàn)榕判驎r,是先用 name 比較大小敬察,如果 name 相同秀睛,則用 age 比較。
還是那句話莲祸,這里舉的例子數(shù)據(jù)量很少蹂安,你可以想象下有一萬個叫“David”的學(xué)生,年齡隨機(jī)分布在 13 到 20 之間锐帜,這時候如果沒有按照 age 進(jìn)行有規(guī)律的存儲田盈,你還是得掃描一萬行數(shù)據(jù)。
只給 student 表建idx_name_age這個復(fù)合索引缴阎,這兩個 sql 語句允瞧,會走索引嗎?
select*fromstudentwherename="David";
select*fromstudentwhereage=18;