索引選擇性
既然索引可以加快查詢(xún)速度碟渺,那么是不是只要是查詢(xún)語(yǔ)句需要,就建上索引事哭?答案是否定的。因?yàn)樗饕m然加快了查詢(xún)速度瓜富,但索引也是有代價(jià)的:索引文件本身要消耗存儲(chǔ)空間鳍咱,同時(shí)索引會(huì)加重插入、刪除和修改記錄時(shí)的負(fù)擔(dān)与柑,另外谤辜,MySQL在運(yùn)行時(shí)也要消耗資源維護(hù)索引蓄坏,因此索引并不是越多越好。一般兩種情況下不建議建索引:
- 表記錄比較少丑念,例如一兩千條甚至只有幾百條記錄的表涡戳,沒(méi)必要建索引,讓查詢(xún)做全表掃描就好了脯倚。至于多少條記錄才算多渔彰,這個(gè)個(gè)人有個(gè)人的看法,我個(gè)人的經(jīng)驗(yàn)是以2000作為分界線推正,記錄數(shù)不超過(guò) 2000可以考慮不建索引恍涂,超過(guò)2000條可以酌情考慮索引。
- 是索引的選擇性較低植榕。所謂索引的選擇性(Selectivity)再沧,是指不重復(fù)的索引值(也叫基數(shù),Cardinality)與表記錄數(shù)(#T)的比值:
Index Selectivity = Cardinality / #T
顯然選擇性的取值范圍為(0, 1]尊残,選擇性越高的索引價(jià)值越大炒瘸,這是由B+Tree的性質(zhì)決定的。例如寝衫,上文用到的employees.titles表顷扩,如果title字段經(jīng)常被單獨(dú)查詢(xún),是否需要建索引竞端,我們看一下它的選擇性:
SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
+-------------+
| Selectivity |
+-------------+
| 0.0000 |
+-------------+
title的選擇性不足0.0001(精確值為0.00001579)屎即,所以實(shí)在沒(méi)有什么必要為其單獨(dú)建索引。
前綴索引
有一種與索引選擇性有關(guān)的索引優(yōu)化策略叫做前綴索引事富,就是用列的前綴代替整個(gè)列作為索引key技俐,當(dāng)前綴長(zhǎng)度合適時(shí),可以做到既使得前綴索引的選擇性接近全列索引统台,同時(shí)因?yàn)樗饕齥ey變短而減少了索引文件的大小和維護(hù)開(kāi)銷(xiāo)雕擂。下面以employees.employees表為例介紹前綴索引的選擇和使用。
從圖中可以看到employees表只有一個(gè)索引<emp_no>贱勃,那么如果我們想按名字搜索一個(gè)人井赌,就只能全表掃描了:
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 |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
如果頻繁按名字搜索員工,這樣顯然效率很低贵扰,因此我們可以考慮建索引仇穗。有兩種選擇,建<first_name>或<first_name, last_name>戚绕,看下兩個(gè)索引的選擇性:
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>顯然選擇性太低纹坐,<first_name, last_name>選擇性很好,但是first_name和last_name加起來(lái)長(zhǎng)度為30舞丛,有沒(méi)有兼顧長(zhǎng)度和選擇性的辦法耘子?可以考慮用first_name和last_name的前幾個(gè)字符建立索引果漾,例如<first_name, left(last_name, 3)>,看看其選擇性:
SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
| 0.7879 |
+-------------+
選擇性還不錯(cuò)谷誓,但離0.9313還是有點(diǎn)距離绒障,那么把last_name前綴加到4:
SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
| 0.9007 |
+-------------+
這時(shí)選擇性已經(jīng)很理想了,而這個(gè)索引的長(zhǎng)度只有18捍歪,比<first_name, last_name>短了接近一半户辱,我們把這個(gè)前綴索引 建上:
ALTER TABLE employees.employees
ADD INDEX `first_name_last_name4` (first_name, last_name(4));
此時(shí)再執(zhí)行一遍按名字查詢(xún),比較分析一下與建索引前的結(jié)果:
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' |
+----------+------------+---------------------------------------------------------------------------------+
性能的提升是顯著的费封,查詢(xún)速度提高了120多倍焕妙。
前綴索引兼顧索引大小和查詢(xún)速度蒋伦,但是其缺點(diǎn)是不能用于ORDER BY和GROUP BY操作弓摘,也不能用于Covering index(即當(dāng)索引本身包含查詢(xún)所需全部數(shù)據(jù)時(shí),不再訪問(wèn)數(shù)據(jù)文件本身)痕届。