上一篇已經講了索引的基本類型,這一篇主要介紹下如何選擇更高效的索引類型。mysql
如今有下面一張學生成績表sql
CREATE TABLE `student` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `first_name` varchar(20) NOT NULL, `last_name` varchar(20) NOT NULL, `created_at` timestamp NOT NULL, `updated_at` timestamp NOT NULL, `score` int(3) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `created_at` (`created_at`) KEY `score` (`score`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
如今咱們要根據學生成績查詢學生姓名,這是一個很簡單的查詢。select first_name,last_name from student where score=99;
這條sql
就使用到了索引score
。
可是咱們一般會看到不少查詢不恰當的使用到索引,最後就致使mysql
沒辦法使用到索引。若是查詢中的不是獨立的,則Mysql不會使用到索引,獨立的列
是指索引列不能是表達式的一部分,也不能是函數的參數。
如select first_name,last_name from student where score+1=100;
這個查詢是不能使用到索引的。
再如:select first_name from student where TO_DAYS(NOW())-TO_DAYS(created_at)>0;
也是不能使用到索引的。segmentfault
有時候須要索引的列是很長的字符串,若是直接建立索引會使索引變的很大這樣就變的比較慢了。改進方式如今能想到就是前面說到的哈希索引
,
可是有時候哈希索引
是不適合的。這個時候就有了前綴索引:把列開始的部分字符串做爲索引,這樣能夠大大的節約索引空間,從而提升索引效率
。但這樣也會下降索引的選擇性。索引選擇性指:不重複的索引值和數據表總數的比值。索引的選擇性越高,那麼索引的查詢效率越高。惟一索引的選擇性最高爲1,性能也是最好的。
對於很長的VARCHAR
,TEXT
這樣的列,若是要做爲索引的話,那麼必須使用前綴索引。
那麼怎麼選擇合適的前綴索引呢。
訣竅在於要選擇足夠長的前綴以保證比較高的索引選擇性,同時又不能太長,由於索引越短,索引空間越小。
如:一張訂單表,要爲聯繫人手機號作前綴索引,這個適合須要分析多少長度的前綴索引,能夠查詢每一個長度的緩存
SELECT COUNT(DISTINCT LEFT(phone,3))/COUNT(*) AS pre3, COUNT(DISTINCT LEFT(phone,4))/COUNT(*) AS pre4, COUNT(DISTINCT LEFT(phone,5))/COUNT(*) AS pre5, COUNT(DISTINCT LEFT(phone,6))/COUNT(*) AS pre6, COUNT(DISTINCT LEFT(phone,7))/COUNT(*) AS pre7, COUNT(DISTINCT LEFT(phone,8))/COUNT(*) AS pre8 FROM orders; +--------+--------+--------+--------+--------+--------+ | pre3 | pre4 | pre5 | pre6 | pre7 | pre8 | +--------+--------+--------+--------+--------+--------+ | 0.0026 | 0.0216 | 0.1397 | 0.3274 | 0.4533 | 0.4533 | +--------+--------+--------+--------+--------+--------+ 1 row in set (0.10 sec)
能夠看到在長度爲7的時候,選擇性的提高已經很小了。這個時候,咱們就能夠考慮取7這個值了。固然這裏只是一個舉例,在實際場景中,手機號的長度徹底能夠直接做爲普通索引的。
前綴索引是比較小並且快,可是Mysql
不能用前綴索引做爲group by
和order by
,也不能作覆蓋掃描(因此查詢必須回表)。函數
對於初學者,常見的錯誤就是爲每一個查詢列都加一個索引,或者按照錯誤的順序建立的多列索引。網上有多說「把where 條件中的列都加上索引就行了」,這種說法是錯誤的。不少時候這樣的索引並不會提升效率。如一個訂單表,狀態有8種,若是爲訂單狀態加上索引。那麼在數據量少的狀況下可能會提升效率,可是當數據量大的時候反而會影響效率。
若有下表:性能
CREATE TABLE `student` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `first_name` varchar(20) NOT NULL, `last_name` varchar(20) NOT NULL, `created_at` timestamp NOT NULL, `score` int(3) NOT NULL DEFAULT '0', `updated_at` timestamp NOT NULL, PRIMARY KEY (`id`), KEY `score` (`score`), KEY `first_name` (`first_name`) ) ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8
如今表中有600萬數據。如今咱們統計分數的出現次數SELECT COUNT(*) AS num,score FROM student GROUP BY score ORDER BY num DESC LIMIT 10;
獲得結果spa
+-------+-------+ | num | score | +-------+-------+ | 68607 | 13 | | 68557 | 44 | | 68551 | 67 | | 68527 | 64 | | 68490 | 35 | | 68490 | 5 | | 68457 | 17 | | 68422 | 50 | | 68415 | 95 | | 68409 | 11 | +-------+-------+ 10 rows in set (2.35 sec)
發現分數爲13的有6萬8千個。這個時候咱們查詢score
爲13,first_name
開始爲O
的,SELECT score,first_name FROM student WHERE first_name LIKE '0%' AND score=13;
能夠獲得結果1173 rows in set (0.76 sec)
;
分析獲得設計
mysql> explain SELECT score,first_name FROM student WHERE first_name LIKE '0%' AND score=13 \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: student partitions: NULL type: ref possible_keys: score,first_name key: score key_len: 4 ref: const rows: 135128 filtered: 3.61 Extra: Using where 1 row in set, 1 warning (0.00 sec)
這個時候能夠看到實際上是隻用到索引score
的,掃描了13萬行數據, 這個時候咱們是單獨爲兩個字段加的索引。code
可是若是咱們建立一個(score,first_name)
的多列索引呢。
獲得結果1173 rows in set (0.00 sec)
;能夠看到多列索引的速度明顯比單獨的索引要不少.
分析排序
mysql> explain SELECT * FROM student WHERE first_name LIKE '0%' AND score=13 \G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: student partitions: NULL type: range possible_keys: score_first_name key: score_first_name key_len: 66 ref: NULL rows: 1173 filtered: 100.00 Extra: Using index condition 1 row in set, 1 warning (0.01 sec)
能夠看到這個時候用到多列索引,掃描行數,明顯的減小了,時間也快了不少。因此說選擇合適的多列索引
在建立多列索引的時候咱們常常須要考慮的就是如何去選擇合適的索引順序,而不是說哪一個查詢條件在前面就選擇哪一個順序。而是應該根據實際狀況來分析考慮,在多是順序下還應該知足排序分組等需求。好比說查詢SELECT score,first_name FROM student WHERE first_name LIKE '0Z%' AND score=13;
這個查詢是應該建立一個(score,first_name)的索引,仍是應該將索引順序顛倒一下呢。咱們能夠查詢一下兩個列的分佈狀況,最後根據分析結果來確認索引的順序。
mysql> SELECT SUM(score='13'),SUM(first_name LIKE '0Z%') FROM student; +-----------------+----------------------------+ | SUM(score='13') | SUM(first_name LIKE '0Z%') | +-----------------+----------------------------+ | 68607 | 3499 | +-----------------+----------------------------+ 1 row in set (2.59 sec)
根據前面在索引選擇性的描述,咱們應該將first_name
放到前面。那咱們在來看看這個狀況下 score
的索引選擇性。
mysql> SELECT SUM(score='13') FROM student WHERE first_name LIKE '0Z%'; +-----------------+ | SUM(score='13') | +-----------------+ | 39 | +-----------------+
能夠看到這把first_name
放到前面是比較符合索引選擇性的規則的。可是也不是全部的場景都是符合這種狀況的。因此仍是須要具體分析。綜合出比較有利的設計方案。否則反而可能形成一些沒必要要的麻煩。
若是一個索引的葉子節點,也就是索引中包含須要查詢行,那麼咱們就稱這個索引是覆蓋索引。
索引中包含須要查詢的行,那麼此次查詢就不會須要回表去查詢數據。若是是二級索引,那麼能夠減小對主鍵的二次查詢。
若是隻須要讀取索引數據,Mysql將會減小很大的數據訪問量。磁盤I/O也會下降不少。若是開啓了緩存,那麼緩存中的數據也會小不少。