1. 程式人生 > 實用技巧 >MySQL - B-Tree索引

MySQL - B-Tree索引

MySQL - B-Tree索引

InnoDB使用B+Tree實現其索引。

關於B-Tree

1B-Tree索引

資料庫中B+Tree的高度一般在2-4之間,即查詢某一鍵值的行記錄時最多進行2-4次IO。

InnoDB儲存引擎中頁的大小為16KB,一般表的主鍵型別為INT(佔用4個位元組)或BIGINT(佔用8個位元組),指標型別也一般為4或8個位元組,也就是說一個頁(B+Tree中的一個節點)中大概儲存16KB/(8B+8B)=1K個鍵值(因為是估值,為方便計算,這裡的K取值為10 ^ 3)。也就是說一個深度為3的B+Tree索引可以維護10 ^ 3 * 10 ^ 3 * 10 ^ 3 = 10億 條記錄。

B+Tree索引分為聚集索引和非聚集索引。

1.1聚集索引(聚簇索引,clustered index)

聚集索引:索引項的排序方式和表中資料記錄排序方式一致的索引。

聚集索引就是按照每張表的主鍵構造一棵B+樹,同時葉子節點中存放的即為整張表的行記錄資料,也將聚集索引的葉子節點稱為資料頁。

聚集索引的順序就是資料的物理儲存順序。它會根據聚集索引鍵的順序來儲存表中的資料,即對錶的資料按索引鍵的順序進行排序,然後重新儲存到磁碟上。因為資料在物理存放時只能有一種排列方式,所以一個表只能有一個聚集索引。

資料頁上存放的是完整的每行的記錄,而在非資料頁的索引頁中,存放的僅僅是鍵值及指向資料頁的偏移量,而不是一個完整的行記錄。

聚集索引的儲存並不是物理上連續的,而是邏輯上連續的。

  • 按順序存放物理資料的成本過高。
  • 資料頁及其內部的資料通過雙向連結串列連線,可以物理上不連續。(連結串列特性)

InnoDB中的聚集索引

  • 如果表定義了主鍵,則主鍵就是聚集索引

  • 如果表沒有定義主鍵,則第一個not NULL unique列是聚集索引

  • 否則,InnoDB會建立一個隱藏的row-id作為聚集索引

聚集索引的優點

  • 資料訪問更快,因為索引和資料放在同一棵B+Tree中。

  • 對於主鍵的排序查詢和範圍查詢速度非常快。

聚集索引的缺點

  • 插入速度嚴重依賴於插入順序,按照主鍵的順序插入是最快的方式,否則將會出現

    頁分裂,嚴重影響效能。因此,對於InnoDB表,我們一般都會定義一個自增的ID列為主鍵

  • 更新主鍵的代價很高,因為將會導致被更新的行移動。因此,對於InnoDB表,我們一般定義主鍵為不可更新。

1.2非聚集索引(輔助索引/二級索引,Secondary Index)

建立在聚集索引上的索引。

Innodb輔助索引的葉子節點並不包含行記錄的全部資料,葉子節點除了包含鍵值外,還包含了相應行資料的聚簇索引鍵(主鍵值)

輔助索引的存在不影響資料在聚簇索引中的組織,所以一張表可以有多個輔助索引。

如果在一棵高度為3的輔助索引樹中查詢資料,那需要對這棵輔助索引樹遍歷3次找到指定主鍵,如果聚集索引樹的高度同樣為3,那麼還需要對聚集索引樹進行3次查詢,最終找到一個完整的行資料所在的頁,因此一共需要6次邏輯IO訪問以得到最終的一個數據頁。

回表查詢:先定位主鍵,再定位資料,效能較低。覆蓋索引可以避免回表查詢。

2索引的管理

SHOW INDEX FROM可以查看錶中索引的資訊。

部分列的含義

  • Non_Unique:是否是非唯一的索引

  • Key_Name:索引名字

  • Seq_in_index:索引中該列的位置,從1開始

  • Collation:列以什麼方式儲存在索引中。對於B+Tree索引,該列總是A,即排序的。使用雜湊索引時為NULL

  • Cardinality:Cardinality/n_row_in_table應儘可能接近1

  • Sub_part:是否為部分索引。整列索引時為NULL

Cardinality

索引的選擇性:按性別進行查詢時,可取值一般只有M、F。因此SQL語句得到的結果可能是該表50%的資料假如男女比例1 : 1,這時新增B+樹索引是完全沒有必要的。相反,如果某個欄位的取值範圍很廣,幾乎沒有重複,屬於高選擇性。則此時使用B+樹的索引是最合適的。例如對於姓名欄位,基本上在一個應用中不允許重名的出現。

索引的選擇性在資料庫中表示為Cardinality/n_row_in_table,該值越接近1,索引的選擇性越高。

由於每種儲存引擎對B+Tree的索引實現不同,對Cardinality統計時放在儲存引擎層進行。

更新策略:Cardinality是一個預估值,不保證準確性。對於更新頻繁的索引,資料庫不可能每次更新都重新計算該值。且對於規模較大的資料,計算該值花費的時間可能較長。

需要更新Cardinality時,可以使用ANALYZE TABLE命令。

快速索引建立(Fast Index Creation,FIC)

MySQL5.5之前,建立索引時需要建立一張新表並把原表中的資料複製到其中,在這期間,資料庫服務不可用。

FIC:對於輔助索引的建立,InnoDB在需要建立索引的引擎上加一個S鎖(共享鎖),此時讀操作是被允許的(寫操作依然不可用)。

對於主鍵索引的建立和刪除,依然需要重建一張表。

3聯合索引(複合索引)

聯合索引即在一張表中有多個索引。

最左字首匹配

4覆蓋索引(covering index)

從輔助索引中就可以得到查詢的記錄,而不需要查詢聚集索引中的記錄,避免了回表查詢。

覆蓋索引是通過聯合索引實現的,其思想很暴力,就是索引即資料

聚合索引和輔助索引的區別在於,聚合索引的葉節點存放了整行記錄,而輔助索引只包含了定義索引的那一列。

注:很容易搞錯的一點,輔助索引的葉節點中雖然沒有像聚集索引那樣包含所有資料,但作為索引,其葉節點一定包含了作為索引的那一列的資料。

對於任一查詢,並不總是要獲得整行記錄,通常只需獲取其中幾列即可。當需要獲取的列作為索引時,其資料已經包含在B+Tree的葉節點中,不需要再到聚集索引中查詢(回表)。

CREATE TABLE user(
id INT(20) NOT NULL AUTO_increment,
name VARCHAR(25) NOT NULL,
psw VARCHAR(255),
PRIMARY KEY(id),
index idx_name(name)
)ENGINE=InnoDB;
  • 執行SQL語句
SELECT id FROM user WHERE name='sqlboy';

此時,由於id是主鍵(聚集索引),name是輔助索引,它們的值都已經包含在索引樹(B+Tree)中,不需要再回表查詢,即索引覆蓋了查詢的需求

  • 執行SQL語句
SELECT psw FROM user WHERE name='crudboy';

此時,psw欄位不是索引,根據條件name='crudboy',走輔助索引name,查詢得到符合條件的記錄對應的主鍵,通過回表在聚集索引樹上找到滿足條件的記錄中的psw。

如果psw是使用很頻繁的欄位,可以將其加入聯合索引,避免回表,這就是覆蓋索引。

5MRR優化(Multi-Range Read)

5.6新特性。

充分利用了磁碟的特性,隨機訪問和順序訪問存在著較大的效能差異,前面提到,InnoDB聚集索引的鍵值在物理上並不連續,只是在邏輯上連續(體現為一個雙向連結串列,連結串列是隨機訪問的)。

InnoDB中MRR的工作方式

  • 將查詢得到的輔助索引鍵值存放於一個快取中,這時快取中的資料是根據輔助索引鍵值排序的。
  • 將快取中的鍵值根據RowID進行排序。
  • 根據RowID的排序順序來訪問實際的資料檔案。

6索引下推(Index Condition Pushdown,ICP)

5.6新特性。

在5.6以前,當進行索引查詢時,首先根據索引來查詢記錄,然後再回表根據WHERE條件來過濾記錄。

依然是這張表(主鍵為id,索引為name,psw)

SELECT * FROM user WHERE name='crud%' and psw=‘123456’;

根據最左字首匹配原則,name是一個範圍,只有name可以用上索引,psw不能走索引,此時根據索引name查詢得到聚集索引的主鍵,回表根據條件psw='123456'來逐個篩選結果。

其實不難發現,psw也是索引,只是對於這個查詢,由於最左字首匹配原則,這個索引沒有能發揮作用,但事實上,psw列的資料依然儲存在索引樹中,而WHERE條件需要的欄位正是psw,此時可以在取出索引的階段立刻過濾不滿足psw='123456'的主鍵。

參考資料:《MySQL技術內幕 InnoDB儲存引擎 第2版》

​     《高效能MySQL》

​     《MySQL實戰45講》