聚簇索引 (主鍵索引)(Clustered Index (Primary Index))
聚簇索引與其說是索引,不如說是InnoDB用來存儲記錄的數據容器更為恰當。
InnoDB中的聚簇索引采用B-Tree組織起來,每個節點都是一個Page(InnoDB存儲記錄的最小單位);非葉節點存 Key 的值和指向孩子節點的指針,葉子節點則存儲記錄和指向相鄰葉節點的指針(所有葉節點構成一個雙向鏈表),下面是一個簡單的示意圖:
InnoDB根據Key值順序存儲記錄,相鄰的Node彼此通過指針連接,這樣有兩個好處:
- 這樣相關的記錄會存儲的比較近,讀取相關記錄的時候只需要Load少數Page就好。例如上圖若要讀取key 5和key 6的記錄,只要加載page 5就好。
- 執行范圍查詢時不用整棵B+樹都掃描一遍,只要找到最小的key值所在的葉節點Page,一直往后讀即可。例如上圖查詢key值在5〜10的記錄,只要找到key 5所在的page 5,然后一直往后讀值到key 10所在的page 7為止。
當然排好序的記錄也有一些缺點,如果插入key值無序的記錄時容易造成性能問題。例如上圖如果插入一條key為13的記錄時,MySQL會直接寫入到page 8;但若是插入一條key為9的記錄時會導致page 7發生頁拆分(Page Split),這種情況下MySQL會在Page之間移動記錄,繼而影響性能。
MySQL並不會把Page的全部空間都用完,相反,它會保留一部分空間為日后添加或更新使用,如上圖的page 6。根據MySQL 5.7 Reference Manual:
If index records are inserted in a sequential order (ascending or descending), the resulting index pages are about 15/16 full. If records are inserted in a random order, the pages are from 1/2 to 15/16 full.
如果index的記錄是順序插入,索引頁(Index Page)的使用率會在15/16左右,但若是亂序插入的話,索引頁的使用率則會在1/2到15/16之間。
由上述原因可以知道,聚簇索引是如何影響范圍查詢、插入記錄的性能以及Page空間的使用率的,所以一個恰當的聚簇索引鍵值(Clustered Key)很重要。MySQL選擇聚簇索引鍵值的規則如下:
- 有Primary Key,選Primary Key;
- 沒有Primary Key,選第一個NOT NULL的UNIQUE Key;
- 若都沒有,則InnoDB生成一個auto increment的隱藏字段做聚簇索引鍵值。
二級索引(Secondary Index)
二級索引同樣使用B-Tree數據結構,不同的是葉節點只存儲二級索引的鍵值和聚簇索引鍵值(通常是Primary Key),聚簇索引鍵值是用於回表查詢該條記錄。
注意到上圖中二級索引鍵值的順序和聚簇索引鍵值順序通常不同,所以二級索引做范圍查詢讀取記錄的性能通常不如聚簇索引高效(回表操作會有大量的隨機IO)。因為二級索引會存儲聚簇索引的鍵值,因此儲聚簇索引鍵值的大小也會影響二級索引的大小,所以在選擇聚簇索引鍵值時需要注意這點。
另外當SELECT的字段被二級索引覆蓋的話,MySQL就不需要再回表查詢了,這樣執行速度更快。例如:
CREATE TABLE `test_table` ( `primary_key` int(11) NOT NULL, `secondary_key` int(11) DEFAULT NULL, `other_key` int(11) DEFAULT NULL, PRIMARY KEY (`primary_key`), KEY `SECONDARY` (`secondary_key`) )
用explain命令分析SELECT primary_key 和 secondary_key 的 SQL,extra 字段顯示 using index,即 MySQL 執行這條語句時直接從索引取值。
反之,則會看到extra字段顯示 using index condition,即需要進行回表取需要的字段值。
小結
索引是個復雜的主題,但通過了解索引底層的運行原理可以幫助我們更精准的使用索引,基本原則如下:
- InnoDB讀取記錄是以Page為單位,加載一個Page只為讀取一條記錄是很浪費且低效的行為。挑選好Primary Key,可以利用訪問局部性(Locality of Reference)提高性能(讓相關的記錄集中在幾個Page里面,這樣InnoDB加載一個Page就可以讀取到多條記錄)。
- 使用二級索引讀取記錄需要進行回表操作,正如同上面第一點提到的,加載一個Page讀取一條記錄是低效的。因此二級索引覆蓋所有需要的字段對性能會有顯著提升。
譯自 A Brief Introduction to Cluster Index and Secondary Index in InnoDB