mysql的索引結構分析

最近重新看了下mysql索引的相關知識,總結一下,下邊以mysql的innodb存儲引擎爲例,其他的存儲引擎會有差別,先簡單說一下b+樹,因爲mysql的索引是以b+樹的結構存儲的,下邊先上張b+樹的結構圖

這張圖是我自己生成的,之前找了個網站可以看到動態出生成b+樹的過程,有興趣的可以研究一下,生成網址,B+樹有以下特點:

  1. 每個節點中子節點的個數不能超過 N,也不能小於 N/2(不然會造成頁分裂或頁合併)
  2. 根節點的子節點個數可以不超過 m/2,這是一個例外
  3. m 叉樹只存儲索引,並不真正存儲數據,只有最後一行的葉子節點存儲行數據。
  4. 通過鏈表將葉子節點串聯在一起,這樣可以方便按區間查找

說明:

  1. 有的資料顯示的葉子節點中的指針是單向的,有的資料顯示葉子節點中的指針是雙向的,我覺得是雙向指針,如果你能確定是那一種的話可以告訴我,咱們一起探討一下。
  2. 上圖中最後一個葉子節點上存了倆數據,其他的節點都存了一個數據,這個節點的大小可以自己設定,對應的是索引的長度,據說在長度爲4的時候性能達到最優

mysql的索引結構和b+樹相同,也就是生成的索引和上圖的形狀一樣,mysql查找數據是以頁爲最小存儲單元進行IO的,每頁數據的大小爲16kb(默認的,大小可以自己調),innodb的所有數據文件(後綴爲ibd的文件),他的大小始終都是16384(16k)的整數倍。假如表中的每條數據大小是1kb,則每次IO可以取出16條數據,mysql的索引分爲兩種:

  1. 聚簇索引:將數據存儲與索引放到了一塊,找到索引也就找到了數據
  2. 非聚簇索引:將數據存儲於索引分開結構,索引結構的葉子節點指向了數據的對應行

innodb中,在聚簇索引之上創建的索引稱之爲輔助索引,輔助索引訪問數據總是需要二次查找,也就是咱們常說的回表操作,非聚簇索引都是輔助索引,像複合索引、前綴索引、唯一索引,輔助索引葉子節點存儲的不再是行的物理位置,而是主鍵值,如果沒有定義主鍵,Innodb會選擇第一個非空的唯一索引代替,如果沒有非空唯一索引,Innodb會隱式定義一個6字節的rowid主鍵來作爲聚集索引。,假如在字段A、B、C上建立了複合索引,則該索引生成的b+樹的葉子節點上存放的東西有該條數據上ABC三個字段的值以及指向主鍵id的指針,下邊對幾種特殊查詢是否會用到索引以及如何通過索引來查找數據進行一下分析:

先建個人信息表,主鍵id,複合索引建立在province、city、area三個字段上邊,sql如下:

-- ----------------------------
-- Table structure for personal_info
-- ----------------------------
CREATE TABLE `personal_info`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `province` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `city` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `area` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `first_index`(`province`, `city`, `area`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of personal_info
-- ----------------------------
INSERT INTO `personal_info` VALUES (1, '張三', '河南', '鄭州', '高新區');
INSERT INTO `personal_info` VALUES (2, '李四', '河南', '鄭州', '二七區');
INSERT INTO `personal_info` VALUES (3, '王五', '河南', '焦作', '三陽區');
INSERT INTO `personal_info` VALUES (4, '趙六', '河南', '鄭州', '金水區');
INSERT INTO `personal_info` VALUES (5, '小明', '河南', '焦作', '解放區');
INSERT INTO `personal_info` VALUES (6, '小紅', '北京', '北京', '朝陽區');
INSERT INTO `personal_info` VALUES (7, '小綠', '北京', '北京', '通州區');
INSERT INTO `personal_info` VALUES (8, '小蘭', '北京', '北京', '海淀區');
INSERT INTO `personal_info` VALUES (9, '小黃', '北京', '北京', '東城區');

上邊有兩個索引,主鍵索引和符合索引first_index,存儲結構分別如下

上邊主鍵id的索引結構是按照從小到大的順序排序的,下邊first_index索引的排序是根據建立字段時候選擇的排序方式進行排序的

一、先來個簡單的sql,

SELECT * FROM personal_info WHERE id = 6;

這個sql用到了主鍵索引,查找順序應該是 5—>7—>6—>6,一共查找了4次就找到了想要的數據,如果沒有使用索引的話就需要全表掃描,查詢8次,這還是數據量不多的情況下,如果有一萬條數據,全表掃描的話就需要查詢一萬次,所以查詢時用索引字段會快,但也不是一定的,下邊會有特殊情況出現

二、上邊的sql升一下級,

SELECT * FROM personal_info WHERE id < 6;

這個sql也用到了主鍵索引,同樣是先查找id=6的數據的位置,查找順序也是 5—>7—>6—>6,找到6在葉子節點上的位置後,根據葉子節點上存儲的向左的指針從而查詢到小於6的所有數據,如果你的where條件是id>6,那麼就會順着向右的指針查詢下去,所以我覺得葉子節點上存儲的指針是雙向的,不是單向的

三、上邊兩條sql用到的是主鍵索引,下邊用下複合索引,大家都知道複合索引的使用有個前提就是最左原則,在查詢的where條件中只有使用了複合索引中最左邊的字段纔會生效,但是隻要用到了最左邊的字段就一定會用複合索引嗎?可以先運行下下邊的sql

SELECT * FROM personal_info WHERE province='河南' 

按說用到了複合索引的第一個字段province,應該會用到複合索引,但是explain分析一下你會發現並沒有用到索引,possible_keys只是可能用到的索引,key中並沒有任何值,所以最後沒有用到索引

說下複合索引的查詢順序,其他的非聚集索引也是同樣的查詢順序,按照b+樹的結構一級一級往下找,發現葉子節點上第1,2,3,4,5條記錄都符合要求,由於複合索引的葉子節點上只存儲了索引字段的值以及指向主鍵索引的指針,而我們要查詢的是所有字段,所以mysql知道id爲1,2,3,4,5的數據都符合條件後又會拿着主鍵id通過主鍵索引去最終找到符合條件的記錄,也就是傳說中的回表操作,但是通過id去找記錄,又會查找5遍主鍵索引,所以最後查詢記錄的次數就成了 查找符合索引的次數+主鍵索引查找*5,我就是全表掃描也就查詢8次,但是用你這個複合索引的話就要查找二十多次,mysql有查詢自動優化的機制,它發現不用索引會更快,就會在查詢的時候不用索引

四、上邊是select * 的情況,下邊在來一條同樣的sql

SELECT province,city,area,id FROM personal_info WHERE province='河南' 

用explain解析一下該條sql

只是改變了一下select的字段而已,但是又用到了複合索引了,這是因爲前邊提到的複合索引的葉子節點上存儲的信息有索引字段的數據和主鍵的數據,也就是葉子節點上存儲了咱們查詢需要的province,city,area,id這四個字段,不需要在通過id去進行回表查詢操作了,只是查詢複合索引就可以找到結果了,mysql發現這種情況下使用索引會比較快,所以就使用了複合索引

五、再說一種比較特殊的情況,模糊查詢,一般情況下認爲模糊查詢不會用到索引,需要進行全表掃描,但是大家看下下邊這條sql有沒有用到索引

SELECT province,city,area,id FROM personal_info WHERE province like '河南%' 

用explain解析一下該條sql

又使用到了索引,前邊不是剛說模糊查詢不會用到索引嗎,怎麼這裏又用到了,難不成在搞我?這是因爲複合索引排序時會先根據第一個字段進行排序,第一個字段相同時根據第二個字段排序,依次類推,查詢時會拿着‘河南’倆字去b+樹中找‘河南’倆字開頭的數據,有與查詢的字段葉子節點中全部存有,所以也不用回表操作了,這樣就避開了全表掃描,所以mysql會選用複合索引。如果把字段換成‘%河南’,在索引樹中查找時由於前綴是%,也就說可以是任意值,所以會將索引中的所有數據全部掃描一遍,mysql認爲這樣是低效率的,所以不會用到索引

六、sql中有用到in的情況

EXPLAIN SELECT province,name FROM personal_info WHERE id in (5,6,7)

用explain解析一下該條sql

用到索引了,之前我一直以爲in條件不會用到索引,後來查一下說是MySQL 在  5.5及以上 版本中,優化器對 in 操作符可以自動完成優化,針對建立了索引的列可以使用索引,沒有索引的列還是會走全表掃描。

上邊就是我最近複習時遇到的幾種特殊情況,我寫文章的水平有限,這玩意又比較抽象,如果你沒看明白的話請給我評論,我在詳細解釋一下,如果以後遇到其他特殊的sql我會補充進來

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章