Mysql索引學習

前言

最近系統學習了一下mysql的索引知識,感覺收穫頗豐,解決了以前的某些疑問,也增加了新的知識。
mysql查詢過程
在這裏插入圖片描述
mysql的索引不是服務器層的內容,而是引擎層實現的,所以每個引擎對索引的實現邏輯是不一樣的。
mysql的引擎大致分爲三類:
• 官方引擎,如MyISAM,Innodb;
• 社區引擎
• 第三方引擎

索引的類型

• B-TREE索引
• 哈希索引
• R-TREE空間數據索引
• 全文索引
• 聚簇索引
• 覆蓋索引
• 其它(不介紹)

B-TREE索引

PS:下面的內容需要了解鏈表和二叉樹知識
簡單描述一下:
二叉查找數,左節點小於根節點,根節點小於由節點
平衡二叉樹,左右高度最大差一
平衡多路查找二叉樹(AVL樹),左節點小於根節點,根節點小於由節點,左右高度最大差一
一般的B-TREE索引
B-TREE索引是一個術語,不是指具體某種結構的索引。比如Innodb使用的是B+Tree結構,NDB集羣使用的是T+TREE結構,但是都叫B-TREE索引。
不同的結構的B-TREE索引性能不同,優缺點也不同,不限定結構來說優缺點都是不嚴謹的。
一般來講,B-TREE是一個平衡多路查找二叉樹,數據結構如下:
在這裏插入圖片描述
每個節點佔用一個盤塊的磁盤空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指針,指針存儲的是子節點所在磁盤塊的地址。兩個關鍵詞劃分成的三個範圍域對應三個指針指向的子樹的數據的範圍域。
模擬查找,查找key=13的數據:

  1. 查找根節點,讀取磁盤1根節點的數據,key只爲17,13小於17,在左子節點;
  2. 讀取磁盤二左子節點的數據,key值分別爲10、16,13大於10,小於16,所以在P4指定的磁盤塊中;
  3. 讀取磁盤塊5的數據,數據是個有順鏈表,從鏈表中查找key等於13的data,返回數據;
    整個查找過程需要三次IO操作,三次內存查找操作,最後鏈表是有序的,可以用二分法查找。三次IO操作是查詢效率的決定因素。
    PS:如果只查找key=17,實際只有1次IO,1次內存查找。

Innodb的B+TREE索引

數據結構如下:
在這裏插入圖片描述
模擬查找,查找key=13的數據:

  1. 查找根節點,讀取磁盤1根節點的數據,key只爲17,13小於17,在左子節點;
  2. 讀取磁盤二左子節點的數據,key值分別爲10、16,13大於10,小於16,所以在P4指定的磁盤塊中;
  3. 讀取磁盤塊5的數據,數據是個有順鏈表,從鏈表中查找key等於13的data,返回數據;
    整個查找過程需要三次IO操作,三次內存查找操作,最後鏈表是有序的,可以用二分法查找。三次IO操作是查詢效率的決定因素。
    PS:如果只查找key=17,實際也是三次IO,三次內存查找。
    B-TREE和B+TREE對比:
    B-TREE優點:
    1、特定key查詢,B-TREE比B+TREE快,IO次數少;
    B-TREE缺點:
    1、數據保存在節點中,data較大時,可能會導致節點變大,磁盤塊無法存儲一個節點,導致一個節點分裂成多個磁盤塊;
    2、查詢性能不穩定,有的快,有的慢,不好優化;
    B+TREE:
    1、查詢性能穩定;
    2、節點不保存data,所以能存儲更多的key;
    PS:
    Innodb一個節點爲一個數據頁,每個數據頁大小爲16k,所以一個三階B+TREE,key爲bigint 8個字節的話,指針按8字節算,一個數據頁,大致可以存儲16K/(8 + 8)= 1000(1KB 按1000算) ,所以一個數據頁大致可以存儲 = 10億條數據;
    索引適用的查詢
    注意:前提條件是不滿足覆蓋索引
    注意:前提條件是不滿足覆蓋索引
    注意:前提條件是不滿足覆蓋索引
    重要的事情說三遍!!!!!!

    測試表:
CREATE TABLE `user` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `name` char(10) NOT NULL,
  `nick_name` varchar(20) NOT NULL DEFAULT '' COMMENT '暱稱',
  `job` varchar(20) NOT NULL DEFAULT '' COMMENT '職業',
  `age` varchar(20) NOT NULL DEFAULT '' COMMENT '年齡',
  PRIMARY KEY (`id`),
  KEY `index_name` (`name`,`nick_name`,`job`)
) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

索引爲:name、nick_name,job
1、首列全值匹配:

explain select * from user where name = 'zh';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "40",
    "ref": "const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

使用了索引
2、非首列全值匹配

explain select * from user where nick_name = 'ligoudan';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ALL",
    "possible_keys": null,
    "key": null,
    "key_len": null,
    "ref": null,
    "rows": 3,
    "filtered": 33.33,
    "Extra": "Using where"
  }
]

不使用索引
3、where 條件全字段,順序和索引一致

explain select * from user where name = 'zh' and nick_name = 'zhanggoudan';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "122",
    "ref": "const,const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

索引正常使用
4、where 條件全字段,順序和索引不一致

explain select * from user where nick_name = 'zhanggoudan'  and name = 'zh';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "122",
    "ref": "const,const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

索引正常使用,所以最左匹配原則說的並不是字段的順序。
5、全字段,首列在where,其它在order by

explain select * from user where name = 'zh' order by  nick_name;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "40",
    "ref": "const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

索引正常使用
6、全字段,首列在order by,其它在where

explain select * from user where nick_name = 'zhanggoudan'  order by  name;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ALL",
    "possible_keys": null,
    "key": null,
    "key_len": null,
    "ref": null,
    "rows": 3,
    "filtered": 33.33,
    "Extra": "Using where; Using filesort"
  }
]

不使用索引
7、首列模糊匹配

explain select * from user where name like '%zh%' ;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "index",
    "possible_keys": null,
    "key": "index_name",
    "key_len": "122",
    "ref": null,
    "rows": 3,
    "filtered": 33.33,
    "Extra": "Using where; Using index"
  }
]

沒有使用索引
8、首列前綴匹配

explain select * from user where name like 'zh%' ;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "range",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "40",
    "ref": null,
    "rows": 1,
    "filtered": 100,
    "Extra": "Using where; Using index"
  }
]

可以使用索引
9、範圍查詢

explain select * from user where name >= 'allo' and name <= 'bill';
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "range",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "40",
    "ref": null,
    "rows": 1,
    "filtered": 100,
    "Extra": "Using index condition"
  }
]

使用了索引,但是使用的是Using index condition,mysql5.6之後的特性
11、多列帶模糊查詢

explain select * from user where name = 'zh' and nick_name = 'lig%' and job = '123' ;
[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "ref",
    "possible_keys": "index_name",
    "key": "index_name",
    "key_len": "204",
    "ref": "const,const,const",
    "rows": 1,
    "filtered": 100,
    "Extra": "Using index condition"
  }
]

使用了索引,但是使用的是Using index condition,mysql5.6之後的特性
10、只有order by
如果order by後的字段滿足上面的條件,也是可以使用索引的。
只測試這麼多場景,更多場景需要自己測試。

哈希索引

哈希索引,只能精確匹配,因爲存儲結構就是哈希表。
數據結構:
在這裏插入圖片描述
查詢場景:
Mysql中只有memory引擎支持哈希索引,所以一般我們永不上。
優點:
1、查詢性能很好,根絕哈希值直接定位數據;
缺點:
1、無法排序,哈希不支持排序;
2、必須精確查詢才能使用索引;
3、哈希衝突問題;

空間索引R-TREE

mysql的MYISAM支持空間索引,用不到,不需要了解。
全文索引FULLTEXT
我暫時不瞭解這部分內容。

前綴索引

有時候數據量比較大且索引子單比較長,這時候可以建前綴索引來減小索引空間的開銷。但是這樣會減小索引的可選擇度。
可選擇度指,補充的值和總記錄數的比值。
所以使用前綴索引的時候,要先確認一個比較合理的前綴。
如何選擇一個合適的長度,以user爲例:
1、先計算列的可選擇性,以name列爲例,

select count(distinct (name)) / count(*) from user;

結果是:0.2308
所以只要選擇前綴越接近約好;
2、計算前綴列的可選擇性

select count(distinct (left(name, 1))) / count(*) from user;
select count(distinct (left(name, 2))) / count(*) from user;

結果是:0.2308、0.2308(數據造的有問題)
按照這個結果來看,只用長度爲1的前綴就能達到目標了。
因爲數據造的有問題,數據更多的時候就能看出不同的長度有明顯的差別了。

多列索引

一般也叫聯合索引或者複合索引,上面的例子已經說明這個索引的情況了。
一般where條件或者order by 涉及多個列的時候,就需要多列索引。

聚簇索引

聚簇索引不是一種單獨的索引類型,而是一種數據存儲方式。
在InnoDB中,聚簇索引保存了B-TREE和數據行,在葉子節點中包含了完整的行數據。
數據結構就是上面B+Tree的數據結構。
PS:InnoDB中沒有非聚簇索引,有二級索引,myisam引擎中有非聚簇索引。
聚簇索引的優缺點:
優點

  1. 聚簇索引獲取數據較爲簡單,可以從將B-Tree中獲取
  2. 聚簇索引支持範圍查詢,
    缺點
  3. 聚簇索引的更新成本比較高,可能會導致“頁分裂”。
  4. 插入速度嚴重依賴於插入順序,按照主鍵進行插入的速度是加載數據到Innodb中的最快方式。如果不是按照主鍵插入,最好在加載完成後使用OPTIMIZE TABLE命令重新組織一下表。
  5. 聚簇索引在插入新行和更新主鍵時,可能導致“頁分裂”問題。
  6. 聚簇索引可能導致全表掃描速度變慢,因爲可能需要加載物理上相隔較遠的頁到內存中(需要耗時的磁盤尋道操作)。
    另外InnoDB建立聚簇索引是通過唯一非空索引座位聚簇索引,一般也就是主鍵id。

覆蓋索引

覆蓋索引不是一種索引類型,可以理解爲一種查詢工具,如果查詢的字段都在索引中,就叫覆蓋索引。
在滿足覆蓋索引的情況下,一般不能使用索引的查詢方式,也能使用索引了,比如:

explain select job from user where job like '%ligoudan%';

分析結果:

[
  {
    "id": 1,
    "select_type": "SIMPLE",
    "table": "user",
    "partitions": null,
    "type": "index",
    "possible_keys": null,
    "key": "index_name",
    "key_len": "204",
    "ref": null,
    "rows": 13,
    "filtered": 11.11,
    "Extra": "Using where; Using index"
  }
]

其它查詢場景也是類似的效果,就不一一列舉了。
這也是爲什麼有時候多列索引不符合最左原則的原因。

使用索引優化查詢

1、創建單列索引還是多列索引?
如果查詢語句中的where、order by、group 涉及多個字段,一般需要創建多列索引,比如:

select * from user where nick_name = 'ligoudan' and job = 'dog';

2、多列索引的順序如何選擇?
一般情況下,把選擇性高德字段放在前面,比如:
查詢sql:

select * from user where age = '20' and name = 'zh' order by nick_name;

這時候如果建索引的話,首字段應該是age,因爲age定位到的數據更少,選擇性更高。
但是務必注意一點,滿足了某個查詢場景就可能導致另外一個查詢場景更慢。
3、避免使用範圍查詢
很多情況下,範圍查詢都可能導致無法使用索引。
4、儘量避免查詢不需要的數據

explain select * from user  where job like '%ligoudan%';
explain select job from user where job like '%ligoudan%';

同樣的查詢,不同的返回值,第二個就可以使用覆蓋索引,第一個只能全表遍歷了。
5、查詢的數據類型要正確

explain select * from user  where create_date >= now();
explain select * from user  where create_date >= '2020-05-01 00:00:00';

第一條語句就可以使用create_date的索引,第二個就不可以。

總結

系統的瞭解學習索引相關的知識,最主要的是索引的數據結構,很多優缺點都是數據結構的體現。

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