1. 程式人生 > >MySQL之索引

MySQL之索引

var 也不能 oss 標題 數據結構 部分 normal 訪問 spa

索引(在MySQL中也叫鍵(key))是存儲引擎用於快速找到記錄的一種數據結構。

索引類型有:Normal,Unique,FullText。

索引方法有:BTREE、HASH。

我有一個user_info的測試表

技術分享

裏面隨機生成了300個姓名

技術分享

一、索引基礎

創建一個普通索引:

mysql> create index myindex on user_info(username);
Query OK, 0 rows affected
Records: 0  Duplicates: 0  Warnings: 0

也可以指定索引的長度:

create index myindex on user_info (username(3
));

創建一個唯一索引:

mysql> create unique index uindex on user_info (id);
Query OK, 0 rows affected
Records: 0  Duplicates: 0  Warnings: 0

創建一個全文索引:(InnoDB引擎對全文索引的支持是5.6版本引入的)

mysql> create fulltext index findex on user_info (username);
Query OK, 0 rows affected
Records: 0  Duplicates: 0  Warnings: 1

到目前為止創建了三個索引:

技術分享

可以看出,默認的索引方法是BTREE。

刪除索引:

mysql> drop index findex on user_info;
Query OK, 0 rows affected
Records: 0  Duplicates: 0  Warnings: 0
mysql> drop index uindex on user_info;
Query OK, 0 rows affected
Records: 0  Duplicates: 0  Warnings: 0

關鍵詞解釋:

主鍵:當我們創建主鍵的時候,同時分配了一個唯一索引,也就是“主索引”,不過與唯一索引不一樣的是,主鍵用關鍵字Primary 而不是 Unique。

外鍵:一般外鍵字段為某個表的主鍵,關鍵字為Foreign Key

普通索引:這種索引是最基本的索引,作用就是加快數據的訪問速度。

唯一索引:普通索引允許數據列包括重復的數據,而唯一索引不允許。

全文索引:該索引可以用於全文搜索。

二、索引設計的原則

1. 最適合索引的列是出現在where子句後面的列,或連接子句中出現的列。

2. 使用唯一索引。考慮列中值的分布。索引的列基數越大,索引的效果越好。

3. 使用短索引。如果對字符串列進行索引,應該指定一個前綴長度。

4. 利用最左前綴。

5. 不要過度索引。

這裏解釋一下最左前綴:

我創建了一個組合索引:

mysql> create index nindex on user_info (username(3),password(6)); Query OK, 0 rows affected Records: 0 Duplicates: 0 Warnings: 0

實際上我相當於建立了兩個索引,分別是:

username,password

username

而沒有password。

因為這是MySQL組合索引“最左前綴”的結果。簡單理解就是只從最左邊開始組合。

例如:

mysql> explain select * from user_info where username = 茹芬慧 and password = 123456;
+----+-------------+-----------+------+----------------+---------+---------+-------+------+------------------------------------+
| id | select_type | table     | type | possible_keys  | key     | key_len | ref   | rows | Extra                              |
+----+-------------+-----------+------+----------------+---------+---------+-------+------+------------------------------------+
|  1 | SIMPLE      | user_info | ref  | myindex,nindex | myindex | 767     | const |    1 | Using index condition; Using where |
+----+-------------+-----------+------+----------------+---------+---------+-------+------+------------------------------------+
1 row in set

mysql> explain select * from user_info where password = 123456;
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | user_info | ALL  | NULL          | NULL | NULL    | NULL |  300 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
1 row in set

第一個使用了索引,d第二個沒有。

三、語句什麽時候執行索引,什麽時候不執行?

為什麽要寫這個,這個標題的意思不是告訴你什麽時候用索引,而是告訴你當你使用索引的時候,索引什麽時候不幹活。

例子1:

mysql> select * from user_info where username like %茹%;
+-----+----------+----------+------------+
| id  | username | password | mydate     |
+-----+----------+----------+------------+
|  24 | 茹芬慧   | 123456   | 2017-04-28 |
| 144 | 茹瑛炫   | 123456   | 2017-04-28 |
+-----+----------+----------+------------+
2 rows in set

mysql> select * from user_info where username like 茹%;
+-----+----------+----------+------------+
| id  | username | password | mydate     |
+-----+----------+----------+------------+
| 144 | 茹瑛炫   | 123456   | 2017-04-28 |
|  24 | 茹芬慧   | 123456   | 2017-04-28 |
+-----+----------+----------+------------+
2 rows in set

這兩個查詢語句都查出了結果,可是到底有沒有好好幹活呢?

mysql> explain select * from user_info where username like %茹%;
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | user_info | ALL  | NULL          | NULL | NULL    | NULL |  300 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
1 row in set

mysql> explain select * from user_info where username like 茹%;
+----+-------------+-----------+-------+---------------+---------+---------+------+------+-----------------------+
| id | select_type | table     | type  | possible_keys | key     | key_len | ref  | rows | Extra                 |
+----+-------------+-----------+-------+---------------+---------+---------+------+------+-----------------------+
|  1 | SIMPLE      | user_info | range | myindex       | myindex | 767     | NULL |    2 | Using index condition |
+----+-------------+-----------+-------+---------------+---------+---------+------+------+-----------------------+
1 row in set

看吧,第一個掃描了全部行,而第二個就令我們滿意了。

mysql> set profiling = 1;
Query OK, 0 rows affected

mysql> select * from user_info where username like %茹%;
+-----+----------+----------+------------+
| id  | username | password | mydate     |
+-----+----------+----------+------------+
|  24 | 茹芬慧   | 123456   | 2017-04-28 |
| 144 | 茹瑛炫   | 123456   | 2017-04-28 |
+-----+----------+----------+------------+
2 rows in set

mysql> select * from user_info where username like 茹%;
+-----+----------+----------+------------+
| id  | username | password | mydate     |
+-----+----------+----------+------------+
| 144 | 茹瑛炫   | 123456   | 2017-04-28 |
|  24 | 茹芬慧   | 123456   | 2017-04-28 |
+-----+----------+----------+------------+
2 rows in set

mysql> show profiles;
+----------+-----------+----------------------------------------------------+
| Query_ID | Duration  | Query                                              |
+----------+-----------+----------------------------------------------------+
|        1 | 0.0005955 | select * from user_info where username like %茹% |
|        2 |  0.000492 | select * from user_info where username like 茹%  |
+----------+-----------+----------------------------------------------------+
2 rows in set

用了索引的快。

結論一:MySQL能在索引中做最左前綴匹配的LIKE比較,因為該操作可以轉換為簡單的比較操作,但是!如果是以通配符開頭的LIKE查詢,就完蛋了。

例子2:

今天重新生成了一下數據,一共798條

技術分享

我想測試一下group by。

mysql> select count(*),mydate from user_info group by mydate;
+----------+---------------------+
| count(*) | mydate              |
+----------+---------------------+
|      300 | 2017-04-30 10:07:04 |
|      199 | 2017-04-30 10:37:19 |
|      299 | 2017-04-30 10:37:34 |
+----------+---------------------+
3 rows in set

把mydate字段加上索引,並再次執行上面的語句

mysql> create index tindex on user_info (mydate);
Query OK, 0 rows affected
Records: 0  Duplicates: 0  Warnings: 0
mysql> select count(*),mydate from user_info group by mydate;
+----------+---------------------+
| count(*) | mydate              |
+----------+---------------------+
|      300 | 2017-04-30 10:07:04 |
|      199 | 2017-04-30 10:37:19 |
|      299 | 2017-04-30 10:37:34 |
+----------+---------------------+
3 rows in set

最後,比較加上索引與不加索引的區別

mysql> show profiles;
+----------+------------+-----------------------------------------------------------+
| Query_ID | Duration   | Query                                                     |
+----------+------------+-----------------------------------------------------------+
|        1 |  0.0007455 | select count(*),username from user_info group by username |
|        2 |   0.003184 | select count(*),password from user_info group by password |
|        3 |   0.000651 | select * from user_info where password = aedd902d       |
|        4 |  0.0002475 | select count(*) from user_info where group by mydate      |
|        5 |  0.0182485 | select count(*) from user_info group by mydate            |
|        6 |  0.0012275 | select count(*),mydate from user_info group by mydate     |
|        7 | 0.00123675 | select count(*),username from user_info group by username |
|        8 |  0.2569555 | create index tindex on user_info (mydate)                 |
|        9 | 0.00111575 | select count(*),mydate from user_info group by mydate     |
+----------+------------+-----------------------------------------------------------+
9 rows in set

只需要看6和9,雖然索引快了一些,但是基本上可以忽略了,這是因為我mydate字段重復的太多,不利於索引。

結論2:索引的選擇性越高則查詢效率越高,因為選擇性高的索引可以讓MySQL在執行的時候過濾掉更多的行。(選擇性=不重復的索引值(也稱為基數)/數據表的記錄總數)

例如我的:

技術分享

username的選擇性為1,效率最高

技術分享

mydate的選擇性為6/798,效率非常差

例子3:

mysql> select * from user_info where id + 1 = 798;
+-----+----------+----------+---------------------+
| id  | username | password | mydate              |
+-----+----------+----------+---------------------+
| 797 | 燕淞可   | 3e68fcdd | 2017-04-30 10:37:34 |
+-----+----------+----------+---------------------+
1 row in set

mysql> select * from user_info where id = 797;
+-----+----------+----------+---------------------+
| id  | username | password | mydate              |
+-----+----------+----------+---------------------+
| 797 | 燕淞可   | 3e68fcdd | 2017-04-30 10:37:34 |
+-----+----------+----------+---------------------+
1 row in set

這兩條查詢語句代表的意思一樣,但是執行起來就一樣嗎?效率差太多了

mysql> show profiles;
|       11 | 0.00116025 | select * from user_info where id + 1 = 798                |
|       12 |   0.000483 | select * from user_info where id = 797                    |

查看Explain

mysql> explain select * from user_info where id + 1 = 798;
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | user_info | ALL  | NULL          | NULL | NULL    | NULL |  798 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
1 row in set

mysql> explain select * from user_info where id = 797;
+----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table     | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | user_info | const | PRIMARY       | PRIMARY | 4       | const |    1 | NULL  |
+----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+
1 row in set

這是因為如果查詢的列而不是獨立的,就不會使用索引。“獨立的列”指的是:不能是表達式的一部分,也不能是函數的一部分。

結論三:查詢的列需要獨立

例子4:

前綴索引,什麽時候用呢。對於BLOB,TEXT或者很長的Varchar,我們就不得不使用前綴索引了,並且MySQL不允許索引這些列的完整長度。

但是!對於前綴索引,多長才合適呢?

首先,計算完整列的選擇性:

mysql> select count(distinct password)/count(*) from user_info;
+-----------------------------------+
| count(distinct password)/count(*) |
+-----------------------------------+
| 1.0000                            |
+-----------------------------------+
1 row in set

(⊙o⊙)…這是個失誤,都怪我這個生成的都好唯一啊。那就幹講吧。

完整列的選擇性 = (目標列不重復數目)/(數據總數)

然後計算前綴的選擇性,並且讓前綴的選擇性接近完整列的選擇性。

前綴的選擇性 = count(distinct left(【列名】,【長度】))/(數據總數)

前綴的選擇性需要去換數值一個一個試的。

等你找到了合適的長度,就可以添加這個索引了

create index yourindex on yourtable (yourcol(yourlength));

不過無法用前綴索引做Group By 和Order By。

結論四:如果需要索引很長的字符列,那就使用前綴索引

例子5:

最近比較忙,未完待續...

MySQL之索引