MySQL索引實現
InnoDB引擎中的索引
非聚集索引型別 | 說明 |
---|---|
單列索引 | 一個索引只包含一個列 |
多列索引(複合索引) | 一個索引包含多個列 |
唯一索引 | 索引列的值必須唯一,允許有一個空值 |
MyISAM引擎中的索引
InnoDB資料檢索過程
上面的表中有2個索引:id作為主鍵索引,name作為輔助索引。
如果需要查詢id=14的資料,只需要在左邊的主鍵索引中檢索就可以了。
MyISAM資料檢索過程
1、在索引中找到對應的關鍵字,獲取關鍵字對應的記錄的地址
2、通過記錄的地址查詢到對應的資料記錄
索引管理和使用
資料準備
請參考第21篇(MySQL全面瓦解21(番外):一次深夜優化億級資料分頁的奇妙經歷)中模擬的千萬資料,我們以這個資料為測試資料。
建立索引
1 create [unique] index index_name on t_name(c_name[(length)]);
create [unique] index index_name on t_name(c_name[(length)]);
刪除索引
1 drop index index_name on t_name;
檢視索引
1 show index from t_name;
索引修改
示例
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
再看看他的執行過程,掃描了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讀取
再看看他的執行過程,一次定位到該條資料:
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、儘量能夠覆蓋常用欄位