目錄
5.1 概述
InnoDB支持以下幾種常見的索引:
-
B+樹索引
-
全文索引
-
哈希索引,自適應的,不能人爲干預是否在一張表中生成哈希索引
注意:
B+樹索引並不能找到一個給定鍵值的具體行,能找到的只是被查找數據所在的頁。然後數據庫通過把頁讀入導內存,再在內存中進行查找,最後得到要查找的數據。
5.2 數據結構與算法
粗略的介紹一下,具體的請查看相關書籍。
5.2.1 二分查找法
用來查找一組有序的記錄數組中的某一記錄。
如有5、10、19、21、31、37、42、48、50、52這10個數,現要從這10個數中查找48這條記錄,其查找過程如下:
每頁Page Directory中的槽是按照主鍵的順序進行存放的,對應某一條具體記錄的查詢是通過對Page Directory進行二分查找得到的(槽前面的博客介紹過)。
5.2.2 二叉查找樹和平衡二叉樹
B+樹是通過二叉查找樹,再由平衡二叉樹,B樹演化而來的。
二叉查找樹的特點:左子樹的鍵值總是小於根的鍵值,右子樹的鍵值總是大於根的鍵值,如圖5-2。同時,二叉樹可以任意構造,所以它可能會按照5-3的方式建立二叉樹,這時該二叉樹幾乎退化成鏈表,查詢效率就降低了。
爲解決這種情況,引入了平衡二叉樹,即:首先滿足二叉查找樹的定義,其次必須滿足任何節點的兩個子樹的高度最大差爲1。要滿足這個條件,就必須對二叉樹進行左旋或者右旋操作來使樹保持平衡,有些時候可能需要旋轉多次,如圖所示。
5.2.3 B+樹
首先來看一棵由數字1-7組成的B+樹。
可以看到B+樹有以下特點:
-
所有記錄都在葉子節點上,並且是順序存放的
-
非葉子節點冗餘了部分葉子節點的數據
-
節點之間通過鏈表進行鏈接
-
一個節點可以存儲多個值
B+樹的插入和刪除操作是通過對B+樹的拆分或者合併來保持樹的平衡,由於篇幅有限,這裏不進行詳細說明,感興趣的可以查看《MySQL技術內幕 InnoDB存儲引擎 第2版》。
5.3 B+樹索引
B+樹索引的本質就是B+樹在數據庫中的實現,在數據庫中,B+樹的高度都一般在2~4層,也就是說查找某一鍵值的行記錄最多隻需要2到4次IO。當前一般機械磁盤每秒至少可以做100次IO,2~4次的IO意味這查詢時間只需0.02~0.04秒。
數據庫中的B+樹索引可以分爲聚集索引(clustered inex)和輔助索引(secondary index) ,但是不管是聚集還是輔助的索引,其內部都是B+樹,即高度平衡的,葉子節點存放着所有的數據。聚集索引與輔助索引不同的是,葉子節點存放的是否是一整行的信息。
5.3.1 聚集索引和非聚集索引
InnoDB存儲引擎表是索引組織表,表中數據是按照主鍵順序存放的。聚集索引就是按照每張表的主鍵構造一棵B+樹,同時葉子節點存放的即爲整張表的行記錄數據,也將聚集索引的葉子節點稱爲數據頁。每個數據頁都通過一個雙向鏈表來進行鏈接。
非聚集索引(也稱輔助索引),其葉子節點並不包含行記錄的全部數據,存放的僅是主鍵和索引鍵。創建的索引,如聯合索引、唯一索引等,都屬於非聚簇索引。
其關係如下圖,圖片來自於網絡。
當通過輔助索引來查找數據時,innodb存儲引擎會通過輔助索引葉子節點獲得主鍵索引的主鍵,然後再通過主鍵索引找到完整的行記錄。
例如在一棵高度爲3的輔助索引樹中查找數據,那需要對這顆輔助索引樹進行3次IO找到指定主鍵,如果聚集索引樹的高度同樣爲3,那麼還需要對聚集索引樹進行3次查找,最終找到一個完整的行數據所在的頁,因此一共需要6次IO訪問來得到最終的數據頁。
5.3.2 B+樹索引的使用
聯合索引
聯合索引指對錶上的多個列進行索引。
首先創建一張表,並且索引列爲(a, b)
create table t (
a int,
b int,
primary key (a),
key idx_a_b (a, b)
)engine=innodb;
向表中插入一些數據,先看一下聯合索引內部的結構。可以看出數據按(a, b)的順序進行了存放,a的順序:1,1,2,2,3,3,b的順序1,2,1,4,1,2,乍看是無序的,但當a值相同時,b是有序的。
查詢的時候,就涉及到了一個非常重要的知識點:最佳左前綴
例如查詢:
-
select * from t where a = 1 and b = 2
,a有序,b有序,顯然可以使用(a, b)這個聯合索引 -
select * from t where a=1
,a有序,也可以使用(a, b)索引 -
select * from t where b = 3
,b整體上是無序的,所以用不到(a, b)索引
注意:
因爲我們查詢的是所有字段,而輔助索引葉子節點僅存儲了索引值和主鍵值,即(primary key1, primary key2,..., key1, key2, ...)
,這個時候需要通過主鍵id回表查詢,補充字段。在某些情況下可以使用覆蓋索引來避免回表查詢。
覆蓋索引
覆蓋索引,即從輔助索引中就可以得到查詢的記錄,而不需要查詢聚集索引中的記錄。好處是覆蓋索引不包含整行記錄的所有信息,故其大小要遠小於聚集索引,因此可以減少大量的IO操作。
覆蓋索引葉子節點存放的數據爲(primary key1, primary key2,..., key1, key2, ...)
,因此,以下查詢都可以僅使用一次輔助聯合索引來完成查詢。
select key2 from table where key1 = XXX;
select primary key2, key2 from table where key1 = XXX;
select primary key1, key2 from table where key1 = XXX;
select primary key2, primary key2, key2 from table where key1 = XXX;
優化
-
索引提示
-
USE INDEX,告訴優化器可以選擇該索引,實際上優化器還是會根據自己的判斷進行選擇。如:
select * from t USE INDEX(a) where a = 1 and b = 2;
-
FORCE INDEX,強制指定某個索引來完成查詢。如:
select * from t FORCE INDEX(a) where a = 1 and b = 2;
-
-
Multi-Range Read(MRR),根據索引進行查詢優化,減少磁盤的隨機訪問,並且將隨機訪問轉化爲較爲順序的數據訪問,可適應於range,ref,eq_ref類型的訪問。在查詢輔助索引時,首先根據得到的查詢結果,按照主鍵進行排序,並按照主鍵排序的順序進行書籤查找。
-
Index Condition Pushdown(ICP),根據索引進行查詢優化,當進行索引查詢時,首先根據索引來查找記錄,然後再根據where條件來過濾數據
5.4 B+樹索引的分裂
B+樹索引頁的分裂並不總是從頁的中間開始,若插入是順序的,從中間記錄分裂會導致頁空間的浪費。若插入是隨機的,則取中間記錄作爲分裂點的記錄。
如下圖,分裂點爲插入記錄本身,向右分裂後僅插入記錄本身,這在自增插入時是普遍存在的一種情況。
5.5 Cardinality值
還是之前的表t,通過show index from t
來查看錶中索引信息。
mysql> show index from t\G;
*************************** 1. row ***************************
Table: t
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: a
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 2. row ***************************
Table: t
Non_unique: 1
Key_name: idx_a_b
Seq_in_index: 1
Column_name: a
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 3. row ***************************
Table: t
Non_unique: 1
Key_name: idx_a_b
Seq_in_index: 2
Column_name: b
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
3 rows in set (0.00 sec)
Cardinality表示選擇性,建立索引的前提是列中的數據都是高選擇性的,因此該值非常關鍵,表示索引中不重複記錄數量的預估值,在實際應用中,該值應儘可能地接近1,如果非常小,那麼用戶需要考慮是否還有必要創建這個索引。
如何對Cardinality進行統計呢?
如果每次索引在發生操作時就對其進行Cardinality的統計,那麼將會給數據庫帶來很大的負擔。所以,數據庫對於Cardinality的統計都是通過採樣的方法來完成的。
採樣過程如下:
-
取得B+樹索引中葉子節點的數量,記爲A
-
隨機取得B+樹索引中的8個葉子節點。統計每個頁不同記錄的個數,即爲P1,P2,...,P8
-
根據採樣信息給出Cardinality的預估值:Cardinality=(P1+P2+...+P8)*A/8
Cardinality更新策略爲:
-
表中1/16的數據已發生過變化
-
某一行發生變化的次數大於2,000,000
-
當執行SQL語句ANALYZE TABLE、SHOW TABLE STATUS、SHOW INDEX以及訪問INFORMATION_ SCHEMA架構下的表TABLES和STATISTICS時,會去更新Cardinality值。若表中的數據量非常大,並且表中存在多個輔助索引時,執行上述這些操作可能會非常慢。雖然用戶可能並不希望去更新Cardinality值。
5.6 全文索引
全文檢索(Full-Text Search)是將存儲於數據庫中的整本書或整篇文章中的任意內容信息查找出來的技術。它可以根據需要獲得全文中有關章、節、段、句、詞等信息,也可以進行各種統計和分析。
通過倒排索引來實現