天天看點

深入解析MySQL:MySQL索引實作和使用

作者:JAVA後端架構
深入解析MySQL:MySQL索引實作和使用

MySQL索引實作

我們知道,MySQL内部索引是由不同的引擎實作的,主要包含InnoDB和MyISAM這兩種,并且這兩種引擎中的索引都是使用b+樹的結構來存儲的。

InnoDB引擎中的索引

Innodb中有2種索引:主鍵索引(也叫聚集索引)、輔助索引(也叫非聚集索引)。

主鍵索引:每個表隻有一個主鍵索引,b+樹結構,葉子節點存儲主鍵的值以及對應整條記錄的資料,非葉子節點不存儲記錄的資料,隻存儲主鍵的值。

當表中未指定主鍵時,MySQL内部會自動給每條記錄添加一個隐藏的rowid字段(預設4個位元組)作為主鍵,用rowid建構聚集索引。聚集索引在MySQL中即主鍵索引。

輔助索引:每個表可以有多個輔助索引,b+樹結構,非聚集索引葉子節點存儲字段(索引字段)的值以及對應記錄主鍵的值,其他節點隻存儲字段的值(索引字段),這就是與聚集索引不同的地方。每個表可以有多個非聚集索引。

MySQL中非聚集索引進一步區分:

深入解析MySQL:MySQL索引實作和使用

MyISAM引擎中的索引

也是B+樹結構,MyISM使用的是非聚簇索引,如下圖,非聚簇索引的兩棵B+樹看上去沒什麼不同,節點的結構完全一緻隻是存儲的内容不同而已,主鍵索引B+樹的節點存儲了主鍵,

輔助鍵索引B+樹存儲了輔助鍵。表資料存儲在獨立的地方,這兩顆B+樹的葉子節點都使用一個位址指向真正的表資料,對于表資料來說,這兩個鍵沒有任何差别。

由于索引樹是獨立的,通過輔助鍵檢索無需通路主鍵的索引樹。

下圖更形象說明這兩種索引的差別,這邊假設了一個存儲4行資料的表。Id為主鍵索引,Name作為輔助索引,圖中清晰的展現了聚簇索引和非聚簇索引的差異。

深入解析MySQL:MySQL索引實作和使用

我們來分析一下圖中資料檢索過程:

InnoDB資料檢索過程

上面的表中有2個索引:id作為主鍵索引,name作為輔助索引。

如果需要查詢id=14的資料,隻需要在左邊的主鍵索引中檢索就可以了。

如果需要搜尋name='Ellison'的資料,需要2步:

1、先在輔助索引中檢索到name='Ellison'的資料,擷取id為14

2、再到主鍵索引中檢索id為14的記錄

輔助索引這個查詢過程在mysql中叫做回表,相對于主鍵索引多了第二步操作。

MyISAM資料檢索過程

1、在索引中找到對應的關鍵字,擷取關鍵字對應的記錄的位址

2、通過記錄的位址查找到對應的資料記錄

對比發現:innodb中最好是采用主鍵查詢,這樣隻需要一次索引,如果使用輔助索引檢索,涉及多一步的回表操作,比主鍵查詢要耗時一些。

而innodb中輔助索引差別于myisam的是:

表中的資料發生變更的時候,會影響其他記錄位址的變化,如果輔助索引中記錄資料的位址,此時會受影響,而主鍵的值一般是很少更新的,當頁中的記錄發生位址變更的時候,對輔助索引是沒有影響的。

索引管理和使用

建立索引

create 方式:

1 create [unique] index index_name on t_name(c_name[(length)]);            

alter表 方式:

1 alter t_name add [unique] index index_name on (cname[(length)]);           

這邊需注意的是:

index_name 代表索引名稱、t_name代表 表名稱、c_name代表字段名稱。

[] 中括号的内容是可以省略的,也就是說 unique 和 length 可以不寫。如果加上了unique,表示建立唯一索引。

如果字段是char、varchar類型,length可以小于字段實際長度,如果是blog、text等長文本類型,必須指定length。

如果tname後面隻寫一個字段,就是單列索引,如果需要寫多個字段,可以使用逗号隔開,這種叫做複合索引。

删除索引

1 drop index index_name on t_name;           

檢視索引

1 show index from t_name;            

索引修改

即先删除索引,再重建索引:drop +create。

示例

emp表中有500W資料 我們用emp來做測試

1 mysql> select count(*) from emp;
2 +----------+
3 | count(*) |
4 +----------+
5 |  5000000 |
6 +----------+
7 1 row in set            

檢視和建立索引

記得我們之前在emp表上做過索引,是以先看一下這個表目前所有的索引

可以看到,目前主鍵字段id和depno字段上都有建立索引

1 mysql> desc emp;
 2 +----------+-----------------------+------+-----+---------+----------------+
 3 | Field    | Type                  | Null | Key | Default | Extra          |
 4 +----------+-----------------------+------+-----+---------+----------------+
 5 | id       | int(10) unsigned      | NO   | PRI | NULL    | auto_increment |
 6 | empno    | mediumint(8) unsigned | NO   |     | 0       |                |
 7 | empname  | varchar(20)           | NO   |     |         |                |
 8 | job      | varchar(9)            | NO   |     |         |                |
 9 | mgr      | mediumint(8) unsigned | NO   |     | 0       |                |
10 | hiredate | datetime              | NO   |     | NULL    |                |
11 | sal      | decimal(7,2)          | NO   |     | NULL    |                |
12 | comn     | decimal(7,2)          | NO   |     | NULL    |                |
13 | depno    | mediumint(8) unsigned | NO   | MUL | 0       |                |
14 +----------+-----------------------+------+-----+---------+----------------+
15 9 rows in set
16 
17 mysql> show index from emp;
18 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
19 | Table | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
20 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
21 | emp   |          0 | PRIMARY       |            1 | id          | A         |     4952492 | NULL     | NULL   |      | BTREE      |         |               |
22 | emp   |          1 | idx_emp_id    |            1 | id          | A         |     4952492 | NULL     | NULL   |      | BTREE      |         |               |
23 | emp   |          1 | idx_emp_depno |            1 | depno       | A         |          18 | NULL     | NULL   |      | BTREE      |         |               |
24 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
25 3 rows in set           

我們在沒有做索引的字段上做一下查詢看看,在500W資料中查詢一個名叫LsHfFJA的員工,消耗 2.239S

深入解析MySQL:MySQL索引實作和使用

再看看他的執行過程,掃描了4952492 條資料才找到該行資料:

1 mysql> explain select * from emp where empname='LsHfFJA';
2 +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
3 | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
4 +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
5 |  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL | 4952492 | Using where |
6 +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
7 1 row in set           

我們在empname這個字段上建立索引

1 mysql> create index idx_emp_empname on emp(empname); 
 2 Query OK, 0 rows affected
 3 Records: 0  Duplicates: 0  Warnings: 0
 4 
 5 mysql> show index from emp;
 6 +-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
 7 | Table | Non_unique | Key_name        | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
 8 +-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
 9 | emp   |          0 | PRIMARY         |            1 | id          | A         |     4952492 | NULL     | NULL   |      | BTREE      |         |               |
10 | emp   |          1 | idx_emp_id      |            1 | id          | A         |     4952492 | NULL     | NULL   |      | BTREE      |         |               |
11 | emp   |          1 | idx_emp_depno   |            1 | depno       | A         |          18 | NULL     | NULL   |      | BTREE      |         |               |
12 | emp   |          1 | idx_emp_empname |            1 | empname     | A         |     1650830 | NULL     | NULL   |      | BTREE      |         |               |
13 +-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
14 4 rows in set            

再看一下這個執行效率,就會發現有質的飛躍:0.001S,就是這麼神奇,學過之前那篇的B+ Tree就知道,它不用從頭開始掃表核對,而是很小次數的io讀取

深入解析MySQL:MySQL索引實作和使用

再看看他的執行過程,一次定位到該條資料:

1 mysql> explain select * from emp where empname='LsHfFJA';
2 +----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-----------------------+
3 | id | select_type | table | type | possible_keys   | key             | key_len | ref   | rows | Extra                 |
4 +----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-----------------------+
5 |  1 | SIMPLE      | emp   | ref  | idx_emp_empname | idx_emp_empname | 22      | const |    1 | Using index condition |
6 +----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-----------------------+
7 1 row in set           

設定合适的索引長度

根據我們之前的了解,每個磁盤塊(disk)存儲的内容是有限的,如果一個頁中可以存儲的索引記錄越多,那麼查詢效率就會提高,是以我們可以指定索引的字段長度。

但并不是越短越好,要保證字元類型字段查詢有足夠高的區分度,如果隻設定了一個長度,反而導緻查詢的相似比對度不高。

長度的原則是要恰到好處,太長索引檔案就會變大,是以要在區分度和長度上做一個平衡。

如果在我們搜尋的内容中,最後的内容是一緻的或者高度一緻的,那我們就可以省略,比如在使用者的email字段上做索引,幾乎前10個字元是不一樣的,結尾限定在 @****,那麼通過前面10個字元就可以定位一個email位址了。

我們在該字段建立索引的時候就可以指定長度為10,這樣相對于整個email字段更短些,查詢效果确卻基本一樣,這樣一個頁中也可以存儲更多的索引記錄。

像我們上面的那個 empname 字段,基本都是6位數的,隻是小部分是超過6位數,而且字尾基本一緻,是以6位數之後的區分度差不多。

有一個判斷 高區分度以及合适長度索引 的通用算法,如下:

1 select count(distinct left(`c_name`,calcul_len))/count(*) from t_name;           

下面是對 empname 做的分析,比對度越高搜尋效率越高:

1 mysql> select count(distinct left(`empname`,3))/count(*) from emp;
 2 +--------------------------------------------+
 3 | count(distinct left(`empname`,3))/count(*) |
 4 +--------------------------------------------+
 5 | 0.0012                                     |
 6 +--------------------------------------------+
 7 1 row in set
 8 
 9 mysql> select count(distinct left(`empname`,4))/count(*) from emp;
10 +--------------------------------------------+
11 | count(distinct left(`empname`,4))/count(*) |
12 +--------------------------------------------+
13 | 0.0076                                     |
14 +--------------------------------------------+
15 1 row in set
16 
17 mysql> select count(distinct left(`empname`,6))/count(*) from emp;
18 +--------------------------------------------+
19 | count(distinct left(`empname`,6))/count(*) |
20 +--------------------------------------------+
21 | 0.1713                                     |
22 +--------------------------------------------+
23 1 row in set
24 
25 mysql> select count(distinct left(`empname`,7))/count(*) from emp;
26 +--------------------------------------------+
27 | count(distinct left(`empname`,7))/count(*) |
28 +--------------------------------------------+
29 | 0.1713                                     |
30 +--------------------------------------------+
31 1 row in set           

删除索引

1 mysql> drop index idx_emp_empname on emp;
 2 Query OK, 0 rows affected
 3 Records: 0  Duplicates: 0  Warnings: 0
 4 
 5 mysql> show index from emp;
 6 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
 7 | Table | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
 8 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
 9 | emp   |          0 | PRIMARY       |            1 | id          | A         |     4952492 | NULL     | NULL   |      | BTREE      |         |               |
10 | emp   |          1 | idx_emp_id    |            1 | id          | A         |     4952492 | NULL     | NULL   |      | BTREE      |         |               |
11 | emp   |          1 | idx_emp_depno |            1 | depno       | A         |          18 | NULL     | NULL   |      | BTREE      |         |               |
12 +-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
13 3 rows in set            

執行完删除指令再檢視,發現索引已經沒了

小結

本文隻是了解索引的基本用法,後面會認真講一講索引的性能分析和優化政策。

總之,理想的索引應該符合以下特征:

1、相對低頻的寫操作,以及高頻的查詢的表和字段上建立索引

2、字段區分度高

3、長度小(合适的長度,不是越小越好)

4、盡量能夠覆寫常用字段

為幫助開發者們提升面試技能、有機會入職BATJ等大廠公司,特别制作了這個專輯——這一次整體放出。

大緻内容包括了: Java 集合、JVM、多線程、并發程式設計、設計模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat等大廠面試題等、等技術棧!

深入解析MySQL:MySQL索引實作和使用

歡迎大家關注公衆号【Java爛豬皮】,回複【666】,擷取以上最新Java後端架構VIP學習資料以及視訊學習教程,然後一起學習,一文在手,面試我有。

每一個專欄都是大家非常關心,和非常有價值的話題,如果我的文章對你有所幫助,還請幫忙點贊、好評、轉發一下,你的支援會激勵我輸出更高品質的文章,非常感謝!

深入解析MySQL:MySQL索引實作和使用

繼續閱讀