MySQL索引進階-聚簇索引和二級索引
索引分類
Mysql中索引主要分為以下幾類:
1)數據結構
分為B+樹索引、hash索引、FULLTEXT索引、R-Tree索引
2)邏輯角度
分為主鍵索引(PRIMARY KEY),唯一索引(UNIQUE),普通索引(INDEX),組合索引(INDEX),全文索引(FULLTEXT)
3)物理存儲
分為聚簇索引和非聚簇索引。
主鍵索引也被稱為聚簇索引(clustered index),也叫作聚集索引。其余都稱呼為非主鍵索引也被稱為二級索引(secondary index),也叫作輔助索引。
一、聚集索引
InnoDB存儲引擎表是索引組織表,即表中數據按照主鍵順序存放。而聚集索引就是按照每張表的主鍵構造一棵B+樹,同時葉子節點中存放的即為整張表的行記錄數據 。
我們也將聚集索引的葉子節點稱為數據頁。聚集索引的這個特性決定了索引組織表中數據也是索引的一部分。同B+樹數據結構一樣,每個數據頁都通過一個雙向鏈表來進行鏈接。
由於實際的數據頁只能按照一顆B+樹進行排序,因此每張表只能擁有一個聚集索引。在多數情況下,查詢優化器傾向於采用聚集索引。因為聚集索引能夠在B+樹索引的葉子節點上直接找到數據。此外,由於定義了數據的邏輯順序,聚集索引能夠特別快地訪問針對范圍值的查詢。查詢優化器能夠快速發現某一段范圍的數據頁需要掃描。
如下圖:
特點:
1)自動建立,一個表只有1個。
2)葉子節點包含所有用戶記錄(包括隱藏列),record_type為0
3)每層節點都是按照主鍵從小到大排序
4)內節點(非葉子節點):存儲主鍵值以及頁號, record_type為1
注意:
聚集索引的存儲並不是物理上的連續,而是邏輯上的連續。這其中有兩點
a. 前面說過的頁通過雙向鏈表鏈接,頁按照主鍵的順序排序,
b. 每個頁中的記錄是通過單向鏈表進行維護的,物理存儲上可以同樣不按照主鍵存儲。
二、二級(輔助)索引
對於輔助索引,葉子節點並不包含行記錄的全部數據。葉子節點除了包含鍵值以外,每個葉子節點中的索引行中還包含了一個書簽(bookmark)。該書簽用來告訴InnoDB存儲引擎哪里可以找到與索引相對應的行數據。由於InnoDB存儲引擎表時索引組織表,因此InnoDB存儲引擎的輔助索引的書簽就是相應行數據的聚集索引鍵。
輔助索引的存在並不影響數據在聚集索引中的組織,因此每張表上可以有多個輔助索引。當通過輔助索引來尋找數據時,InnoDB存儲引擎會遍歷輔助索引並通過葉級別的指針獲得指向主鍵索引的主鍵,然后再通過主鍵索引來找到一個完整的行記錄。
如下圖:
特點:
1)手動創建,可以有多個
2)內節點包含索引列、主鍵列、頁號(page_no)
3)葉子節點只包含索引列以及記錄主鍵的值
4)每層節點都是按照索引列的值從小到大排序(索引列值相同時按照主鍵排序)
三、聯合索引
聯合索引是一種特殊的二級索引。聯合索引指的是同時對多列創建的索引,創建聯合索引后,葉子節點會同時包含每個索引列的值,並且同時根據多列排序,這個排序和我們所理解的字典序類似。
每個葉子節點同時保存了所有的索引列,除此之外,還是只包含了主鍵id。
如下圖:
特點:
1)手動創建,可以有多個
2)內節點包含索引列、主鍵列、頁號
3)葉子節點只包含索引列以及記錄主鍵的值
4)每層節點先按照索引中的第1列排序。第1列值相等時,按第2列排序。第2列值相等時,按第3列排序 依次類推,所有列都相等時按照主鍵排序。
四、覆蓋索引-covering index
即從輔助索引中就可以得到查詢的記錄,而不需要查詢聚集索引中的記錄。使用覆蓋索引的一個好處是輔助索引不包含整行記錄的所有信息,故其大小要遠小於聚集索引,因此可以減少大量的IO操作。
簡單來說,覆蓋索引指的就是只需要在一棵索引樹上就能獲取SQL所需的所有列數據,無需回表,速度更快。
覆蓋索引是一種非常強大的工具,能大大提高查詢性能,只需要讀取索引而不用讀取數據有以下一些優點:
1)索引項通常比記錄要小,所以MySQL訪問更少的數據
2)索引都按值的大小順序存儲,相對於隨機訪問記錄,需要更少的I/O
3)覆蓋索引對於InnoDB表尤其有用,因為InnoDB使用聚集索引組織數據,如果二級索引中包含查詢所需的數據,就不再需要在聚集索引中查找了。
五、常見問題解答
比前提:test表中有c1,c2,c3這列,建立聯合索引(c1,c2,c3)
1、 索引的命中與查詢sql中字段的順序有關嗎?
比如 select * from test where c1=2 and c2=5; 與 select * from test where c2=5 and c1=2?
答案:與查詢的字段順序無關,因為查詢優化器會對搜索字段順序跟索引字段順序不一致的sql進行優化。
2、最左前綴原則如何理解,它的原理是什么?
最左前綴原是指從索引的有效命中來說,並不是觸發。只要是索引,或者某個聯合索引的一部分,就會被觸發,具體命中了那些索引字段,要根據key_len來做判斷。
原理:
我們從上面的聯合索引介紹中看到,它的排序方式是:每層節點先按照索引中的第1列排序。第1列值相等時,按第2列排序。第2列值相等時,按第3列排序 依次類推,所有列都相等時按照主鍵排序。
所以我們可以這樣來分析:
對於聯合索引中c1字段是放在最前面的,所以c1是完全有序的,但是c1不知情的條件下,對於c2字段就是無序的,沒辦法排序。因此只有當c1相同的時候,c2字段的索引排序才是完全有序的。
因此,在聯合索引中你只有使用以下的規則的方式查詢才會使用到索引:
c1
c1 ,c2
c1, c2, c3
我們再思考一下,select * from test where c2>=5 and c3=7; 這條sql語句會命中索引么?
分析:這里使用了c2和c3這兩個字段作為查詢條件,但是沒有使用c1字段,因為在c1不知情的條件下,對於c2是無序的。對於c2>=5的條件可能在很多的c1不同中都有符合條件的出現,所以就沒有辦法使用索引,這也是索引實現的原因,一定要遵循「查找有序,充分利用索引的有序性」。
再比如 select * from test where c1>=5 and c2=9;
分析:這個查詢語句中,c1列索引會被命中,c2列卻不會,只有c1列值相等時,才會按c2列排序。但是因為這里的c1>=5,那么c1列是不確定的,后面也就無法按照c2來排序,c2不會被命中。
3、前導模糊查詢為什么會導致索引失效?
比如 select * from test where c1 like '%d%';
字符串的查詢是對字符串里面的字符一個一個的匹配,「若是字符串最左邊為%表示一個不確定的字符串,那么是沒辦法利用到索引的有序性」。
但是若是修改為 :where c1 like 'd%';就可以使用索引,因為最左邊的字符串是確定的,這種稱為「匹配列前綴」。
補充說明:實際業務場景中聯合索引的創建,「我們應該把識別度比較高的字段放在前面,提高索引的命中率,充分的利用索引」。
4、 如何解決數量占表記錄比重較大,查詢優化器放棄索引,直接全表掃描?
通過限制查詢條數來避免索引失效,比如 select * from test where c2=8 limit 10;
5、為什么MySQL的InnoDB引擎采用B+樹而不是B樹?
因為B樹不管葉子節點還是非葉子節點,都會保存數據,這樣導致在非葉子節點中能保存的指針數量變少。指針少的情況下要保存大量數據,只能增加樹的高度,導致IO操作變多,查詢性能變低;
6、二級索引與null值的關系
值為NULL的二級索引記錄被放在了B+樹的最左邊。這是因為InnoDB的設計中有這樣的規定:
We define the SQL null to be the smallest possible value of a field.
翻譯:我們把SQL中的NULL值認為是列中最小的值。
六、如何挑選索引
1、只為用於搜索、排序或分組的列創建索引
2、考慮列的基數
列基數:列值不重復的行數,基數越大索引效果越好
3、索引列的類型盡量小
1)數據類型越小,在查詢時進行的比較操作越快
2)數據類型越小,數據頁內就可以放下更多的記錄,從而減小磁盤I/O帶來的性能損耗
4、索引字符串的前綴
5、索引列在比較表達式中單獨出現
6、主鍵順序插入(綜合評估頁分裂、回表、索引樹大小)
七、索引總結:
1、B+樹索引在空間和時間上都有代價,所以必須合理創建索引
2、B+樹索引適用於下邊這些情況:全值匹配
1)匹配左邊的列
2)匹配范圍值
3)精確匹配某一列並范圍匹配另外一列
4)用於排序
5)用於分組
3、在使用索引時需要注意下邊這些事項
1) 只為用於搜索、排序或分組的列創建索引
2) 為列的基數大的列創建索引
3) 索引列的類型盡量小
4) 可以只對字符串值的前綴建立索引
5) 只有索引列在比較表達式中單獨出現才可以適用索引
6) 為了盡可能少的讓聚簇索引發生頁面分裂和記錄移位的情況,建議讓主鍵擁有auto_increment屬性
7) 定位並刪除表中的重復和冗余索引
8)盡量使用覆蓋索引進行查詢,避免回表帶來的性能損耗。
這里有幾點要注意下:
1)字符串比較大小:逐字比較,費時,效率低,不建議。
2)key_len表示索引中使用的字節數,查詢中使用的索引的長度(最大可能長度),並非實際使用長度,理論上長度越短越好。key_len是根據表定義計算而得的,不是通過表內檢索出的。
參考鏈接:
https://www.dyxmq.cn/databases/mysql/clustered-nonclustered-union-and-unique-indexes-in-mysql.html