InnoDB 索引漫談

InnoDB存儲引擎支持事務,是一個通用的、平衡了高可用與高性能的存儲引擎。它的設計目標主要面向在線事務處理(OLTP)的應用。它的特點有行鎖設計、支持外鍵、支持類似Oracle的非鎖定讀等。

InnoDB存儲引擎支持以下幾種常見索引:B+樹索引、全文索引、哈希索引。我們平時在開發(fā)中使用最多的,就是B+樹索引。
B+樹索引是傳統(tǒng)意義上的索引,這是目前關系數(shù)據(jù)庫系統(tǒng)中最常用和最有效的索引。需要注意的是,B+樹索引并不能找到一個給定鍵值的具體行。B+樹索引能夠找到的只是被查找的數(shù)據(jù)行所在的頁。然后通過數(shù)據(jù)庫把頁讀入內(nèi)存,在內(nèi)存中進行查找,最后得到所需的數(shù)據(jù)。

B+樹索引是基于B+樹數(shù)據(jù)結構構建的,B+樹的數(shù)據(jù)結構在很多數(shù)據(jù)結構書中都有詳細描述,在此不再詳述。數(shù)據(jù)庫中的B+樹索引可以分為聚集索引和輔助索引,它們的內(nèi)部都是高度平衡的,葉子存放著所有的數(shù)據(jù)的樹結構。聚集索引與輔助索引不同的是,葉子節(jié)點存放的是一整行信息。

聚集索引(Cluster Index)

聚集索引就是按照每張表的主鍵構建一個B+樹,同時葉子節(jié)點存放的即為整張表的行記錄數(shù)據(jù),我們也將聚集索引的葉子節(jié)點稱為數(shù)據(jù)頁。聚集索引的結構決定了索引組織表中數(shù)據(jù)也是索引的一部分。由于B+樹索引本身是有序的,同時數(shù)據(jù)行也存儲在葉子節(jié)點上,因此通過聚集索引我們能夠很快的訪問針對范圍值的查詢。

注:

  • 當你在表中創(chuàng)建了一個PRIMARY KEY時,InnoDB就將該索引作為了聚集索引,因此最好為任何一個你創(chuàng)建的表添加PRIMARY KEY。
  • 如果一個表中沒有定義一個PRIMARY KEY,MySQL會把表中第一個非NULL且唯一的索引作為聚集索引。
  • 如果表中沒有符合上述兩種情況的索引,InnnoDB就會隱式的在包含rowID的合成列上生成聚集索引。

(也即不管如何,InnoDB的表中一定會有一個聚集索引,不管是顯示還是隱式創(chuàng)建的)

由于實際的數(shù)據(jù)頁只能按照一顆B+樹進行排序,因此每張表只能擁有一個聚集索引。在多數(shù)情況下,查詢優(yōu)化器傾向于采用聚集索引,因為聚集索引能夠在B+樹葉子節(jié)點上直接找到數(shù)據(jù)。(聚集索引對主鍵的排序查找和范圍查找速度非常快。因為葉子節(jié)點就維護了數(shù)據(jù)行。)圖一展示了一個聚集索引的數(shù)據(jù)分布(所有數(shù)據(jù)行就直接在葉子節(jié)點上):


聚集索引(局部)

輔助索引(Secondary Index)

InnoDB中所有除聚集索引以外的所有索引都被稱為輔助索引。對于輔助索引,葉子節(jié)點并不包含行記錄的全部數(shù)據(jù)。葉子節(jié)點除了包含鍵值以外,每個葉子節(jié)點的索引行中還包含一個主鍵值。通過主鍵值InnoDB存儲引擎可以再次在聚集索引中找到對應索引項的行數(shù)據(jù)。輔助索引的存在不會影響數(shù)據(jù)在聚集索引中的組織,因此每張表上可以存在多個輔助索引。當通過輔助索引來查找數(shù)據(jù)時,InnoDB存儲引擎會遍歷輔助索引并獲得頁中的指向主鍵索引的主鍵,然后再通過主鍵索引依次找到一個完整的行記錄。因此,這就意味著通過輔助索引查找行,存儲引擎先找到輔助索引的葉子節(jié)點,得到對應的主鍵值,然后根據(jù)該主鍵值去聚集索引上查找對應的行數(shù)據(jù)。也即一次輔助索引查找需要兩次對B+樹進行查找。
輔助索引數(shù)據(jù)分布圖示:


輔助索引

B+樹索引的管理

索引的創(chuàng)建刪除通過兩種方法,一種是ALTER TABLE,一種是CREATE/DROP INDEX,具體語法如下:

CREATE INDEX | KEY idx_b ON t
ALTER TABLE t ADD INDEX | KEY idx_b (b(100));//前綴索引
ALTER TABLE t ADD INDEX | KEY idx_a_b (a,b);//聯(lián)合索引
ALTER TABLE t DROP INDEX | KEY idx_b;
DROP INDEX idx_b ON T;//刪除索引

通過SQL命令

SHOW INDEX FROM t;

可以查看表t上的索引情況,圖示如下:

該結果每行的含義如下:

列名 含義
Table 索引所在的表
Non_unique 非唯一索引(PRIMARY KEY為0,因為他必須是唯一的)
key_name 索引名字,我們可以通過該名字來執(zhí)行DROP INDEX
Seq_in_index 索引中該列的位置
Column_name 索引列的名字
Collation 列以什么方式存在索引中。B+樹總是A,即排序的。若使用Heap存儲引擎,且建立了Hash索引,會顯示NULL,即非排序
Cardinality 表示索引中唯一值的數(shù)目的估計值
Sub_part 是否是列的部分被索引(即前綴索引),否則為NULL
Packed 關鍵字如何被壓縮,沒有則為NULL
NULL 索引列是否含有NULL值,YES表示該列允許為NULL
Index_type 索引類型。InnoDB存儲引擎只支持B+樹索引,這里都是顯示BTREE
Comment 注釋

在所有這些參數(shù)中,Cardinality的值十分關鍵,優(yōu)化器會根據(jù)該值來判斷是否使用該索引。但并非每次更新索引都會更新該值,這樣做的代價太大。如果我們需要主動去更新該值,可以使用ANALYZE TABLE命令。

這里,我們會多了解一下Cardinality值的意義:Cardinality值表示索引中不重復記錄的預估值。雖然該值是一個預估值,但是對于我們是否需要在某列上建立索引具有很重要的指導意義。有一個參數(shù)Cardinality/n_rows_in_tables應該盡可能的接近1,此時表明該列的值基本是相互唯一的,表明該列具有很高的選擇性,在該列上建立索引并通過該列進行條件查詢,可以極大的改善查詢速度,如果該值過于小,我們就需要考慮是否有必要在該列上建立索引。舉一個例子:

CREATE TABLE test(
    id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '',
    customer_id BIGINT(20) NOT NULL COMMENT '客戶ID',
    staff_id BIGINT(20) NOT NULL COMMENT '職員ID',
    sex TINYINT(1) DEFAULT 0 COMMENT '性別0-女 1-男',
    company VARCHAR(50) NOT NULL COMMENT '公司名稱'.
    PRIMARY KEY (id),
    KEY idx_sex (sex),
    KEY idx_cus_sta (customer_id,staff_id)
)ENGINE=InnoDB CHARSET=utf8;

如該表t所示,sex列只有兩種情況,可以想象在一般情況下通過SHOW INDEX查詢該表得到sex列的Cardinality的值一定是2,Cardinality/n_rows_in_tables的值一定非常小(接近于0)以sex為條件進行查詢時,每次也只能篩選一半的符合條件的數(shù)據(jù)記錄,該列的選擇性很低,一般沒有必要在這種列上建立索引。

在InnoDB存儲引擎中,Cardinality統(tǒng)計信息的更新發(fā)生在兩個操作中:INSERT和UPDATE。如果每次發(fā)生INSERT和UPDATE操作就進行Cardinality信息統(tǒng)計更新,會給數(shù)據(jù)庫帶來很大的負擔,因此InnoDB存儲引擎內(nèi)部對Cardinality值的更新策略是:

  • 表中1/6數(shù)據(jù)發(fā)生了變化;
  • stat_modified_counter>2000 000 000 ;這是為了應對這樣一種情況,表中某一行記錄頻繁的更新,但實際上表中的數(shù)據(jù)行數(shù)并沒有增加,故在InnoDB存儲引擎內(nèi)部有一個計數(shù)器stat_modified_counter,用來表示發(fā)生變化的次數(shù),大于2000 000 000同樣更新Cardinality的值。

InnoDB存儲引擎內(nèi)部對Cardinality值的統(tǒng)計和更新操作是通過采樣方法的。InnoDB存儲引擎默認對8個葉子節(jié)點進行采樣,過程如下:

  • 取得B+樹索引中葉子節(jié)點的數(shù)目,記為N
  • 隨機取得B+樹索引中的8個葉子節(jié)點,統(tǒng)計每個頁中不同記錄的個數(shù),記為M1,M2,M3...M8
  • Cardinality=(M1+M2+...+M8)*N/8

很明顯我們可以看出,Cardinality值是通過對8個葉子節(jié)點預估而來。根據(jù)它的計算方法,我們可以知道Cardinality值是在不斷變化的。

B+樹索引的使用

在OLTP應用中,我們的查詢操作一次只是從數(shù)據(jù)庫中獲取一小部分數(shù)據(jù),如根據(jù)主鍵值查詢訂單信息,這就是一種典型的OLTP查詢。在這種情況下,通過該主鍵建立的B+樹索引能夠很快的獲取對應數(shù)據(jù)。在實際使用中,我們可能會用到以下一些索引。

  • 前綴索引

對于Mysql中的字符列,我們可以選擇在某些列上建立前綴索引。因為有些時候,索引列很長,這會使得索引變得大且慢,通常這時候,我們可以索引該字符列開始的部分字符,這樣可以大幅節(jié)約索引空間,提高索引效率,但是需要注意的是,這樣也可能會降低索引的選擇性。因此,前綴索引的目標應該是:要選擇足夠長的前綴以保證高的選擇性,同時前綴不要太長(以節(jié)約空間)還是以test表為例,我們在company字符列上創(chuàng)建一個前綴索引:

ALTER TABLE test ADD INDEX idx_com company(20);//示例,前綴長度按實際情況選擇

具體在company字段上選擇多長的前綴呢?這個需要我們實際去試一試。

SELECT COUNT(DISTINCT LEFT(company,4))/COUNT(*) AS sel4,
       COUNT(DISTINCT LEFT(company,5))/COUNT(*) AS sel5,
       COUNT(DISTINCT LEFT(company,6))/COUNT(*) AS sel6,
       ......
       FROM test;

通過對比sel4,sel5,sel6...的結果,就可以找到一個比較合適的前綴長度。

  • 聯(lián)合索引

聯(lián)合索引是指在多個列上建立的索引。如下sql語句:

ALTER TABLE t ADD INDEX | KEY idx_a_b (a,b);//聯(lián)合索引

在B+樹中,所有鍵值都是排序的,而對于聯(lián)合索引,索引列的順序意味著索引首先按照最左列進行排序,其次是第二列,依次等等。因此,索引的順序非常重要。一個簡單的聯(lián)合索引的例子如下圖所示:


聯(lián)合索引

當不需要考慮排序和分組時,將選擇性最高的列放在索引的前面通常是更好的。這么做可以最快的過濾出所需要的行。以前面的test表為例,如果我們想要建立一個包含customer_idstaff_id的聯(lián)合索引,我們可以先比較一下兩個列的選擇性,可以執(zhí)行一下SQL語句:

SELECT COUNT(DISTINCT coustomer_id)/COUNT(*) AS customer_id_selectivity,
       COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity 
FROM test;

比較customer_id_selectivitystaff_id_selectivity哪個值更接近1,也即對應列的選擇性就更高,該列就更適合放在聯(lián)合索引的前面。

  • 覆蓋索引

InnoDB支持覆蓋索引(覆蓋索引并非是一種可以通過SQL語句創(chuàng)建的索引,與前綴索引、聯(lián)合索引不同,我們可以認為這是一種邏輯上的索引,即符合一定條件的索引我們都可以稱之為是覆蓋索引),即從輔助索引中就可以直接得到查詢的記錄,而不需要再次查詢聚集索引中的記錄。使用覆蓋索引的一個好處就是輔助索引不包含整行記錄,因此它的大小遠小于聚集索引,可以減少大量IO操作,查詢速度很快。

不是所有的索引都能夠成為覆蓋索引,覆蓋索引必須要存儲索引列的值。當我們發(fā)起一個一個被索引覆蓋的查詢時,在EXPLAIN的Extra列可以看到Using Index信息,表示該次查詢在索引上就直接獲得了所有需要的數(shù)據(jù),也即這是一個覆蓋索引。如下圖所示:


由于customer_id與staff_id同時存在于索引中,因此當查詢customer_id與staff_id時,就可以直接從輔助索引中可以獲取所需的字段,而不需要再次去查找聚集索引。

最后

InnoDB存儲引擎是我們平時使用最廣泛的MySQL存儲引擎。同時B+樹索引也是我們默認選擇的索引類型。正確的創(chuàng)建和使用索引是實現(xiàn)高性能查詢的基礎。而這些合理的索引操作是基于我們對InnoDB存儲引擎的內(nèi)部原理有一定的了解之上的。

本文只是關于InnoDB索引的簡單總結,如有問題,歡迎大家一起交流、討論。

參考

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容