Mysql索引優化(一)_索引類型(索引策略)

上一篇已經講了索引的基本類型,這一篇主要介紹下如何選擇更高效的索引類型。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,性能也是最好的。
對於很長的VARCHARTEXT這樣的列,若是要做爲索引的話,那麼必須使用前綴索引。
那麼怎麼選擇合適的前綴索引呢。
訣竅在於要選擇足夠長的前綴以保證比較高的索引選擇性,同時又不能太長,由於索引越短,索引空間越小。
如:一張訂單表,要爲聯繫人手機號作前綴索引,這個適合須要分析多少長度的前綴索引,能夠查詢每一個長度的緩存

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也會下降不少。若是開啓了緩存,那麼緩存中的數據也會小不少。

3272554994-5d1cacbae7a7f_articlex

相關文章
相關標籤/搜索