1. 程式人生 > >MySQL索引背後的資料結構及演算法原理(七)

MySQL索引背後的資料結構及演算法原理(七)

索引選擇性與字首索引

既然索引可以加快查詢速度,那麼是不是隻要是查詢語句需要,就建上索引?答案是否定的。因為索引雖然加快了查詢速度,但索引也是有代價的:索引檔案本身要消耗儲存空間,同時索引會加重插入、刪除和修改記錄時的負擔,另外,MySQL在執行時也要消耗資源維護索引,因此索引並不是越多越好。一般兩種情況下不建議建索引。

第一種情況是表記錄比較少,例如一兩千條甚至只有幾百條記錄的表,沒必要建索引,讓查詢做全表掃描就好了。至於多少條記錄才算多,這個個人有個人的看法,我個人的經驗是以2000作為分界線,記錄數不超過 2000可以考慮不建索引,超過2000條可以酌情考慮索引。

另一種不建議建索引的情況是索引的選擇性較低。所謂索引的選擇性(Selectivity),是指不重複的索引值(也叫基數,Cardinality)與表記錄數(#T)的比值:

Index Selectivity = Cardinality / #T

顯然選擇性的取值範圍為(0, 1],選擇性越高的索引價值越大,這是由B+Tree的性質決定的。例如,上文用到的employees.titles表,如果title欄位經常被單獨查詢,是否需要建索引,我們看一下它的選擇性:

SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
+-------------+
| Selectivity |
+-------------+
|      0.0000 |
+-------------+

title的選擇性不足0.0001(精確值為0.00001579),所以實在沒有什麼必要為其單獨建索引。

有一種與索引選擇性有關的索引優化策略叫做字首索引,就是用列的字首代替整個列作為索引key,當前綴長度合適時,可以做到既使得字首索引的選擇性接近全列索引,同時因為索引key變短而減少了索引檔案的大小和維護開銷。下面以employees.employees表為例介紹字首索引的選擇和使用。

從圖12可以看到employees表只有一個索引,那麼如果我們想按名字搜尋一個人,就只能全表掃描了:

EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 300024 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+

如果頻繁按名字搜尋員工,這樣顯然效率很低,因此我們可以考慮建索引。有兩種選擇,建或,看下兩個索引的選擇性:

SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.0042 |
+-------------+
SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.9313 |
+-------------+

顯然選擇性太低,選擇性很好,但是first_name和last_name加起來長度為30,有沒有兼顧長度和選擇性的辦法?可以考慮用first_name和last_name的前幾個字元建立索引,例如,看看其選擇性:

SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.7879 |
+-------------+

選擇性還不錯,但離0.9313還是有點距離,那麼把last_name字首加到4:

SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.9007 |
+-------------+

這時選擇性已經很理想了,而這個索引的長度只有18,比短了接近一半,我們把這個字首索引 建上:

ALTER TABLE employees.employees
ADD INDEX `first_name_last_name4` (first_name, last_name(4));

此時再執行一遍按名字查詢,比較分析一下與建索引前的結果:

SHOW PROFILES;
+----------+------------+---------------------------------------------------------------------------------+
| Query_ID | Duration   | Query                                                                           |
+----------+------------+---------------------------------------------------------------------------------+
|       87 | 0.11941700 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' |
|       90 | 0.00092400 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' |
+----------+------------+---------------------------------------------------------------------------------+

效能的提升是顯著的,查詢速度提高了120多倍。

字首索引兼顧索引大小和查詢速度,但是其缺點是不能用於ORDER BY和GROUP BY操作,也不能用於Covering index(即當索引本身包含查詢所需全部資料時,不再訪問資料檔案本身)。