本文首發於公眾號:Hunter後端
原文連結:MySQL面試必備一之索引
在面試過程中,會有一些關於 MySQL 索引相關的問題,以下總結了一些:
- MySQL 的資料儲存使用的是什麼索引結構
- B+ 樹的結構是什麼樣子
- 什麼是複合索引、聚簇索引、覆蓋索引
- 什麼是最左匹配原則
- 資料 B+ 樹中是如何查詢的
- 回表是什麼操作
- B+ 樹的查詢有什麼優勢
- 索引下推是什麼意思
對於上面這幾個問題,看完這篇筆記你應該就會明白這些問題應該如何作答。
這篇筆記將從以下幾個方面開始介紹:
- B+樹
- 查詢資料的過程
- 覆蓋索引
- 聯合索引
- MyISAM 的儲存結構
- InnoDB 與 MyISAM 的區別
- B 樹與 B+ 樹
1、B+樹
MySQL 的儲存引擎包括 InnoDB、MyISAM、Memory 等,其中 InnoDB 是預設的表儲存引擎,InnoDB 和 MyISAM 的資料都儲存在 B+ 樹這種結構中。
首先來了解下 B+ 樹的結構。
1. B+ 樹結構
與二叉樹一樣,B+ 樹也是一種樹結構,與之不同的點在於每一層並非只有左右兩個子結點,而是可以有多個結點,而在 InnoDB 中,資料都是儲存在葉子結點上,現在假設我們有一張 user 表,以 id 為主鍵,有 name、age 這兩個欄位,那麼資料的儲存示意圖則如下:
在上圖中,只展示了 B+ 樹的兩層,每一層有四個子結點,所有的資料都儲存在葉子結點上。
在 InnoDB 中,B+ 樹的高度通常為 2 到 4 層,假設每一層我們有 1000 個結點,那麼如果樹有 4 層,那麼在第四層的葉子結點我們可以儲存 1000 的三次方條資料,那麼就可以儲存 10 億條資料。
2. 主鍵索引與聚簇索引
當我們建立一張表,並往裡面新增資料,資料儲存的格式就是上面這張圖的形式,預設以主鍵作為索引,也就是 B+ 樹的非葉子結點,所以又稱其為主鍵索引。
對於這張表,除了 id 欄位,還有 age 和 name 欄位,這幾個表欄位是一起存放在葉子結點的,因此這種儲存方式也稱為聚簇索引。
3. 非聚簇索引與二級索引
在 InnoDB 中,除了主鍵索引外,我們還可以為表的某個或者某些欄位建立索引,對於這些索引,我們稱其為非聚簇索引或者二級索引。
二級索引的儲存結構也是 B+ 樹,不一樣的點在於非葉子結點的值是建立了索引的欄位值,葉子結點就不再是這條表資料了,而是這個索引欄位所在的資料的主鍵值。
還是以前面的 user 表為例,我們在 name 欄位上建立索引,那麼 InnoDB 會額外建立一個 B+ 樹,B+ 樹的結構大致如下:
這裡為了更直白的表示欄位值的排序,用了英文名字來表示,如上就是一個二級索引的儲存形式。
2、查詢資料的過程
1. 根據主鍵查詢
比如我們要查詢 id = 80 的資料,sql 語句如下:
select id, name, age from user where id = 80;
這裡是針對 id 欄位進行查詢,所以可以直接查詢主鍵索引,根據上面主鍵索引的示意圖,其查詢步驟如下:
- 根據 id = 18 逐層找到 B+ 樹的對應非葉子結點,比如這裡就到了圖裡的最上層結點
- 根據 id = 18,判斷 1 <18 < 35,所以查詢進入的葉子結點將會進入最左側往下
- 在最左側的葉子結點,找到 id = 18 的葉子結點,然後這個節點對應的 id,name,age 欄位獲取然後返回
以上就是一次根據主鍵進行查詢的過程
2. 根據二級索引查詢
還是 user 表,在 name 欄位上建立了一個二級索引,我們想要找到 name = "Hunter" 的 id,name,age 欄位:
select id, name, age from user where name = "Hunter";
其查詢過程如下:
- 根據 name="Hunter" 查詢二級索引的 B+ 樹,也就是我們的第二張圖,根據非葉子結點的值找到最左側的資料
- 根據 "Hunter" 可以找到這條資料的主鍵 id = 3
- 根據 id = 3 去主鍵索引的 B+ 樹裡查詢對應的欄位(這裡的查詢操作就是根據主鍵查詢資料了)
回表:上面的查詢過程中,根據二級索引獲取到的主鍵 id,到主鍵索引裡查詢對應的資料,這個過程就稱為回表
。
3、覆蓋索引
對上面二級索引查詢的過程,我們有一個回表的操作,即根據二級索引獲取到的主鍵 id 再去主鍵索引獲取相應的欄位資料,這部分的查詢過程是會影響查詢效率的。
那麼針對這種回表的情況,我們可以在某些情況下使用覆蓋索引來對其進行最佳化。
所謂覆蓋索引,並非某種實際的索引結構,而應該算得上是一種思想或者最佳化手段。其主要思想為在二級索引中就可以拿到查詢所需的全部欄位,而不需要進行回表操作。
針對前面我們對 name 加了索引的情況,如果我們的 SQL 語句如下,那麼即可使用覆蓋索引,而不需要再到主鍵索引裡回表查詢:
select id, name from user where name = "Hunter";
在上面這個語句中,我們查詢的是 id 和 name 兩個欄位,這兩個欄位在 name 欄位的二級索引的查詢中即可獲取所需的欄位值,那麼則不需要進行回表操作,這個過程就相當於使用了覆蓋索引。
而如果我們所需要的欄位並不只是這兩個欄位,比如我們還要查詢 age 欄位,針對這種情況,如果要用覆蓋索引的話,就需要引入下一節的內容,聯合索引,或者叫複合索引。
4、聯合索引
複合索引,或者叫聯合索引,指的是針對多個欄位建立的索引,常常適用於多個欄位進行查詢的場景。
1. 聯合索引的結構
聯合索引也是二級索引,不過它的非葉子結點的值是多個欄位組合的。
還是以 user 表為例,我們在 age 和 name 上建立一個聯合索引:
CREATE INDEX age_name_idx ON user (age, name);
那麼,在 MySQL 中,這個二級索引的儲存結構大致如下:
根據上面的這個結構,我們可以知道,欄位的順序是十分重要的,如果我們 SQL 語句的使用不當可能就會用不上聯合索引。
2. 聯合索引生效的情況
根據上圖可以看到非葉子結點的值由兩部分組成,分別是 age 和 name 的值,那麼我們在進行查詢的時候,也應該遵循這個順序才可能使得索引生效。
1) 單個欄位查詢
如果我們要查詢單個欄位,比如 age,那麼下面的條件都可以使得索引生效:
where age = 23;
where age > 25;
但是如果 where 後面的條件是針對 name 欄位,那麼下面的條件則不會使得這個聯合索引生效:
where name = "Hunter";
2) 多個欄位查詢
如果是多個欄位查詢,那麼則使用的時候一定要注意查詢的順序,下面的條件的是可以生效的:
where age = 23 and name = "Hunter";
where age = 24 and name like "Hun*";
而如果是 age 是一個範圍查詢,則不管 name 欄位是什麼條件,這個索引也可以生效,但僅僅是 age 欄位會用到索引,name 欄位的則不會用到索引,比如:
where age > 34 and name = "Hunter";
上面這個 SQL 語句,索引則只會對 age 欄位生效進行範圍查詢,name 欄位不用用到索引的精確匹配。
3) 最左匹配原則
基於聯合索引的結構,如上圖,最左匹配原則的概念其實就顯而易見了,即聯合索引只會從建立了索引的最左欄位開始匹配,直到遇到範圍查詢則停止,就比如上面提到的這條:
where age > 34 and name = "Hunter";
它匹配到 age 就停止了,因為 age 是一個範圍查詢。
再來一種情況,如果我們的聯合索引欄位有三個,按照順序為 age, name 和 field_3,下面的語句則會分別匹配到 age、name 和 field_3:
where age > 4 and name = "Hunter" and field_3 = 45;
where age = 34 and name like "Hun*" and field_3 = 45;
where age = 34 and name = "Hunter" and field_3 > 45;
下面這幾種針對後面幾個欄位的查詢聯合索引都是不生效的:
where name = "Hunter" and field_3 = 45;
where field_3 = 45;
因為他們都沒有從聯合索引的最左欄位欄位開始查詢。
3. 索引下推
索引下推是 MySQL 5.6 版本及以上引入的一個特性,主要用於減少回表的次數,從而實現提高查詢效能的效果。
還是以前面 age 和 name 這個複合索引為例,SQL 如下:
where age > 30 and name like "Hun*";
如果沒有索引下推,那麼它的查詢流程是根據 age > 30 這個條件,查詢主鍵 id,逐個回表去主鍵索引裡查詢 name 欄位的值是否滿足 "Hun*" 這個條件。
而如果有索引下推這個最佳化,那麼在二級索引裡,查詢出 age > 30 的值後,會直接根據複合索引中 name 的值來判斷是否滿足 "Hun*" 這個特性,滿足的話就去回表查詢,不滿足則在當前複合索引裡直接將這條資料過濾掉。
所謂的索引下推就是透過這個流程來減少回表的次數,以提高查詢的效能。
5、MyISAM 的儲存結構
MyISAM 的資料儲存結構也是 B+ 樹,但有一點不同,那就是 InnoDB 的葉子結點儲存的是完整的一條資料,而 MyISAM 的葉子結點儲存的資料的指標,透過指標指向底層儲存的資料,其大概示意圖如下:
同理,MyISAM 的二級索引的葉子結點也是直接指向儲存的資料。
因此,MyISAM 在底層儲存的表檔案有三個,一個是 frm,是表的定義檔案,一個 MYD,用於儲存資料的檔案,一個是 MYI,用於儲存索引的檔案。
相對來說,InnoDB 就只有兩個,一個是 frm,一個是 IBD,用於儲存索引和資料,因為 InnoDB 的葉子結點即儲存了資料。
6、InnoDB 與 MyISAM 的區別
這裡總結一下 InnoDB 與 MyISAM 的區別:
- InnoDB 是聚簇索引,MyISAM 是非聚簇索引
- 即 InnoDB 的主鍵索引是資料和索引放在一起的,而 MyISAM 是索引和資料分離的
- InnoDB 支援外來鍵,MyISAM 不支援外來鍵
- InnoDB 支援事務,MyISAM 不支援事務
- InnoDB 預設支援到行級鎖,而 MyISAM 支援表級鎖
7、B 樹與 B+ 樹
B 樹和 B+ 樹是很相似的樹結構,都是每個結點都有多個子結點,不一樣的在於 B 樹的非葉子結點也存有資料,而 B+ 樹只有葉子結點才有資料,非葉子結點都是索引資料,且 B+ 樹的葉子結點之間也形成有序連結串列。
針對以上這個不同點,在 MySQL 中使用 B+ 樹有如下優點:
- B+ 樹在非葉子結點不包含資料,因此在記憶體頁可以存放更多 key,從而在查詢的時候可以減少 IO 次數
- B+ 樹的葉子結點之間形成連結串列,遍歷操作會更方便,而 B 樹需要從根節點從上往下遍歷
- B+ 樹的資料都存放在葉子結點,而 B 樹的非葉子結點也都有資料,所以查詢的過程中 B+ 樹總是需要訪問到葉子結點,所以 B+ 樹的查詢效率會更穩定
如果想獲取更多後端相關文章,可掃碼關注閱讀: