1. 程式人生 > >MySQL索引的原理,B+樹、聚集索引和二級索引的結構分析

MySQL索引的原理,B+樹、聚集索引和二級索引的結構分析

  索引是一種用於快速查詢行的資料結構,就像一本書的目錄就是一個索引,如果想在一本書中找到某個主題,一般會先找到對應頁碼。在mysql中,儲存引擎用類似的方法使用索引,先在索引中找到對應值,然後根據匹配的索引記錄找到對應的行。

  我們首先了解一下索引的幾種型別和索引的結構。

索引型別

B樹

  大多數儲存引擎都支援B樹索引。b樹通常意味著所有的值都是按順序儲存的,並且每一個葉子也到根的距離相同。B樹索引能夠加快訪問資料的速度,因為儲存引擎不再需要進行全表掃描來獲取資料。下圖就是一顆簡單的B數。

 

 

B樹的查詢流程:
如上圖我要從找到E字母,查詢流程如下:
(1)獲取根節點的關鍵字進行比較,當前根節點關鍵字為M,E<M(26個字母順序),所以往找到指向左邊的子節點(二分法規則,左小右大,左邊放小於當前節點值的子節點、右邊放大於當前節點值的子節點);
(2)拿到關鍵字D和G,D<E<G 所以直接找到D和G中間的節點;
(3)拿到E和F,因為E=E 所以直接返回關鍵字和指標資訊(如果樹結構裡面沒有包含所要查詢的節點則返回null);
(4)通過指標資訊取出這條記錄的所有資訊;

 

B+樹

下圖為B+樹的結構,B+樹是B樹的升級版,我們可以觀察一下,B樹和B+樹的區別是什麼?

B+樹和B樹的區別是:

1. B樹的節點中沒有重複元素,B+樹有。

2. B樹的中間節點會儲存資料指標資訊,而B+樹只有葉子節點才儲存。

3. B+樹的每個葉子節點有一個指標指向下一個節點,把所有的葉子節點串在了一起。

 

從下圖我們可以直觀的看到B樹和B+樹的區別:紫紅色的箭頭是指向被索引的資料的指標,大紅色的箭頭即指向下一個葉子節點的指標。

我們假設被索引的列是主鍵,現在查詢主鍵為5的記錄,模擬一下查詢的過程:

B樹,在倒數第二層的節點中找到5後,可以立刻拿到指標獲取行資料,查詢停止。

B+樹,在倒數第二層的節點中找到5後,由於中間節點不存有指標資訊,則繼續往下查詢,在葉子節點中找到5,拿到指標獲取行資料,查詢停止。

 

B+樹每個父節點的元素都會出現在子節點中,是子節點的最大(或最小)元素。葉子節點儲存了被索引列的所有的資料。

那B+樹比起B樹有什麼優點呢?

1. 由於中間節點不存指標,同樣大小的磁碟頁可以容納更多的節點元素,樹的高度就小。(資料量相同的情況下,B+樹比B樹更加“矮胖”),查詢起來就更快。
2. B+樹每次查詢都必須到葉子節點才能獲取資料,而B樹不一定,B樹可以在非葉子節點上獲取資料。因此B+樹查詢的時間更穩定。
3. B+樹的每一個葉子節點都有指向下一個葉子節點的指標,方便範圍查詢和全表查詢:只需要從第一個葉子節點開始順著指標一直掃描下去即可,而B樹則要對樹做

中序遍歷

 

瞭解了B+樹的結構之後,我們對一張具體的表做分析:

create table Student(
    last_name varchar(50) not null, 
    first_name varchar(50) not null, 
    birthday date not null, 
    gender int(2) not null, 
    key(last_name, first_name, birthday)
);

對於表中的每一行資料,索引中包含了name,birthday列的值。下圖顯示了該索引的結構:

索引對多個值進行排序的依據是create table語句中定義索引時列的順序,即如果名字相同,則根據生日來排序。

 

B+樹的結構決定了這種索引對以下型別的查詢有效:

全值匹配
和索引中所有的列進行匹配,例如查詢姓名為Cuba Allen,生日為1960-01-01的人。


匹配最左字首
查詢姓為Allen的人,即只用索引的第一列。


匹配列字首
匹配某一列的值的開頭部分,例如查詢所有以J開頭的姓的人。


匹配範圍值
查詢姓在Allen和Barrymore之間的人。


精確匹配某一列並範圍匹配另外一列
查詢姓為Allen,名字是字母K開頭的人。即第一列last_name全匹配,第二列first_name範圍匹配。


只訪問索引的查詢
查詢只需要訪問索引,無需訪問資料行。這種索引叫做覆蓋索引。

 

一些限制:

  • 如果不是按照索引的最左列開始查詢,無法使用索引。例如上面例子中的索引無法用於查詢某個特定生日的人,因為生日不是最左資料列。也不能查詢last_name以某個字母結尾的人。
  • 不能跳過索引的列。上述索引無法用於查詢last_name為Smith並且某個特定生日的人。如果不指定first_name,則mysql只能使用索引的第一列。
  • 如果查詢中有某個列的範圍查詢,則右邊所有的列都無法使用索引優化查詢。例如查詢WHERE last_name=’Smith’ AND first_name LIKE ‘J%’ AND birthday=‘1996-05-19’,這個查詢只能使用索引的前兩列。

 

雜湊索引

  雜湊索引,只有精確匹配索引所有列的查詢才有效。對於每一行資料,儲存引擎都會對所有的索引列計算一個雜湊碼。雜湊索引將所有的雜湊碼儲存在索引中,同時在雜湊表中儲存指向每個資料行的指標。如果多個列的雜湊值相同,索引會以連結串列的方式存放多個指標記錄到同一個雜湊條目中。
因為索引自身只儲存對應的雜湊值,所以索引的結構十分緊湊,雜湊索引查詢的速度非常快。但是雜湊索引也有它的限制:

  • 雜湊索引不是按照索引順序儲存的,無法用於排序。
  • 不支援部分索引列匹配查詢。
  • 不支援範圍查詢。

 

聚集索引(clusterd index)

  每個儲存引擎為InnoDB的表都有一個特殊的索引,叫聚集索引。聚集索引並不是一種單獨的索引型別,而是一種資料儲存方式。當表有聚集索引的時候,它的資料行實際上存放在葉子頁中。一個表不可能有兩個地方存放資料,所以一個表只能有一個聚集索引。
  因為是儲存引擎負責實現索引,因此不是所有的儲存引擎都支援聚集索引。InnoDB表中聚集索引的索引列就是主鍵,所以聚集索引也叫主鍵索引。
例如下面這張InnoDB表:

create table Student(
    id int(11) primary key auto_increment,
    last_name varchar(50) not null, 
    first_name varchar(50) not null, 
    birthday date not null
);

聚集索引(主鍵索引)的結構如下圖:

這是一課B+樹,它的葉子頁包含了行的全部資料,節點頁只包含了索引列(即主鍵)。

 

二級索引(secondary indexes)

  對於InnoDB表,在非主鍵列的其他列上建的索引就是二級索引(因為聚集索引只有一個)。二級索引可以有0個,1個或者多個。二級索引和聚集索引的區別是什麼呢?二級索引的節點頁和聚集索引一樣,只存被索引列的值,而二級索引的葉子頁除了索引列值,還存這一列對應的主鍵值。

 

InnoDB和MyISAM的資料分佈對比

以下表為例,我們看下InnoDB和MyISAM是如何儲存這個表的:

create table layout_test(
    col1 int(11) primary key, 
    col2 int(11) not null, 
    key(col2)
);

 

InnoDB表的資料分佈

聚集索引(主鍵索引)分佈如下:

可以看到,葉子節點儲存了整個表的資料,而不是隻有索引列,每個葉子節點包含了主鍵值、事務ID、用於事務和MVCC的回滾指標以及所有的剩餘列(col2)。

 

二級索引分佈如下:

二級索引的葉子節點中儲存的不是“行指標”,而是主鍵值,並以此作為指向行的“指標”。這樣的策略減少了當出現行移動或者資料頁分裂時二級索引的維護工作。使用主鍵當做指標會讓二級索引佔更多空間,但好處是InnoDB在移動行時無需更新二級索引中的這個指標。

 

MyISAM表的資料分佈

col1列上的索引:

col2列上的索引:

實際上MyISAM中主鍵索引和其他索引在結構上沒有什麼不同。

 

從下圖可以看出InnoDB和MyISAM儲存資料和索引的區別。

 

聚集索引的優點:

  • 可以把相關資料儲存在一起,例如實現電子郵箱時,根據使用者ID來聚集資料,讀取少數的資料頁就能獲取某個使用者的全部郵件。
  • 聚集索引將索引和資料儲存在同一個B樹中,因此從聚集索引中獲取資料比在非聚集索引中要快一些。

聚集索引的缺點:

  • 插入速度嚴重依賴插入順序。按照主鍵的順序插入是載入資料到InnoDB表中速度最快的方式。假如磁碟中的某一個已經存滿了,但是新增的行要插入到這一頁當中,儲存引擎就會把該也分裂成兩個頁面來容納該行,這就是一次頁分裂操作。頁分裂會導致表佔用更多的磁碟空間。
  • 更新聚集索引列的代價很高,會強制InnoDB將每個被更新的行移動到新的位置。
  • 用二級索引訪問資料需要兩個索引查詢,不是一次。因為要先從二級索引的葉子節點獲得主鍵值,再根據這主鍵去聚集索引中查到對應的行,所以需要兩次B樹查詢。

 

順序主鍵的策略:

  在InnoDB表中使用自增主鍵是既簡單效能又高的策略,這樣可以保證資料按順序寫入。最好避免隨機的聚集索引,從效能的角度考慮,使用UUID來作為聚集索引是很糟糕的,這樣不僅插入行花費的時間長,而且索引佔用的空間也更大。

 

參考資料:

《高效能mysql(第三版)》

https://stackoverflow.com/questions/870218/differences-between-b-trees-and-b-trees

https://dev.mysql.com/doc/refman/8.0/en/innodb-index-types.html

https://www.jianshu.com/p/1f2560f0e87f