SQL必知必會(索引的使用原則)

創建索引有哪些規律?

創建索引有一定的規律。當這些規律出現的時候,我們就可以通過創建索引提升查詢效率,下面我們來看看什么情況下可以創建索引:

1. 字段的數值有唯一性的限制,比如用戶名

索引本身可以起到約束的作用,比如唯一索引、主鍵索引都是可以起到唯一性約束的,因此在我們的數據表中,如果某個字段是唯一性的,就可以直接創建唯一性索引,或者主鍵索引。

2. 頻繁作為 WHERE 查詢條件的字段,尤其在數據表大的情況下

在數據量大的情況下,某個字段在 SQL 查詢的 WHERE 條件中經常被使用到,那么就需要給這個字段創建索引了。創建普通索引就可以大幅提升數據查詢的效率。

我之前列舉了 product_comment 數據表,這張數據表中一共有 100 萬條數據,假設我們想要查詢 user_id=785110 的用戶對商品的評論。

如果我們沒有對 user_id 字段創建索引,進行如下查詢:

SELECT comment_id, product_id, comment_text, comment_time, user_id 
FROM product_comment WHERE user_id = 785110;

你能看到查詢效率還是比較低的。當我們對 user_id 字段創建索引之后

SELECT comment_id, product_id, comment_text, comment_time, user_id 
FROM product_comment_copy1 WHERE user_id = 785110;

效率提升還是明顯的。

3. 需要經常 GROUP BY 和 ORDER BY 的列

索引就是讓數據按照某種順序進行存儲或檢索,因此當我們使用 GROUP BY 對數據進行分組查詢,或者使用 ORDER BY 對數據進行排序的時候,就需要對分組或者排序的字段進行索引

比如我們按照 user_id 對商品評論數據進行分組,顯示不同的 user_id 和商品評論的數量,顯示 100 個即可。

如果我們不對 user_id 創建索引,執行下面的 SQL 語句:

SELECT user_id, count(*) as num FROM product_comment group by user_id limit 100;

如果我們對 user_id 創建索引,再執行 SQL 語句:

SELECT user_id, count(*) as num FROM product_comment group by user_id limit 100;

你能看到當對 user_id 創建索引后,得到的結果中 user_id 字段的數值也是按照順序展示的,運行時間卻不到原來時間的 1/40,效率提升很明顯。

同樣,如果是 ORDER BY,也需要對字段創建索引。我們再來看下同時有 GROUP BY 和 ORDER BY 的情況。比如我們按照 user_id 進行評論分組,同時按照評論時間降序的方式進行排序,這時我們就需要同時進行 GROUP BY 和 ORDER BY,那么是不是需要單獨創建 user_id 的索引和 comment_time 的索引呢?

當我們對 user_id 和 comment_time 分別創建索引,執行下面的 SQL 查詢:

SELECT user_id, count(*) as num FROM product_comment_copy2 group by user_id order by comment_time desc limit 100;

實際上多個單列索引在多條件查詢時只會生效一個索引(MySQL 會選擇其中一個限制最嚴格的作為索引),所以在多條件聯合查詢的時候最好創建聯合索引。在這個例子中,我們創建聯合索引 (user_id, comment_time),再來看下查詢的時間,查詢時間為 0.3s,效率提升了很多。如果我們創建聯合索引的順序為 (comment_time, user_id) 呢?運行時間為 0.7s,同樣比兩個單列索引要快,但是會比順序為 (user_id, comment_time) 的索引要慢一些。這是因為在進行 SELECT 查詢的時候,先進行 GROUP BY,再對數據進行 ORDER BY 的操作,所以按照這個聯合索引的順序效率是最高的。

兩個單索引comment_time, user_id

聯合索引(user_id,comment_time

聯合索引(comment_time,user_id

4.UPDATE、DELETE 的 WHERE 條件列,一般也需要創建索引

我們剛才說的是數據檢索的情況。那么當我們對某條數據進行 UPDATE 或者 DELETE 操作的時候,是否也需要對 WHERE 的條件列創建索引呢?

如果我們想要把 comment_text 為 462eed7ac6e791292a79 對應的 product_id 修改為 10002,當我們沒有對 comment_text 進行索引的時候,執行 SQL 語句:

UPDATE product_comment SET product_id = 10002 WHERE comment_text = '462eed7ac6e791292a79';

如果我們對 comment_text 字段創建了索引,然后再把剛才那條記錄更新回 product_id=10001,執行 SQL 語句:

UPDATE product_comment SET product_id = 10001 WHERE comment_text = '462eed7ac6e791292a79';

如果我們對某條數據進行 DELETE,效率如何呢?

比如我們想刪除 comment_text 為 462eed7ac6e791292a79 的數據。當我們沒有對 comment_text 字段進行索引的時候,執行 SQL 語句:

DELETE FROM product_comment WHERE comment_text = '462eed7ac6e791292a79';

如果我們對 comment_text 創建了索引,再來執行這條 SQL 語句

DELETE FROM product_comment_copy5 WHERE comment_text = '462eed7ac6e791292a79';

對數據按照某個條件進行查詢后再進行 UPDATE 或 DELETE 的操作,如果對 WHERE 字段創建了索引,就能大幅提升效率。原理是因為我們需要先根據 WHERE 條件列檢索出來這條記錄,然后再對它進行更新或刪除。如果進行更新的時候,更新的字段是非索引字段,提升的效率會更明顯,這是因為非索引字段更新不需要對索引進行維護。

不過在實際工作中,我們也需要注意平衡,如果索引太多了,在更新數據的時候,如果涉及到索引更新,就會造成負擔。

5.DISTINCT 字段需要創建索引

有時候我們需要對某個字段進行去重,使用 DISTINCT,那么對這個字段創建索引,也會提升查詢效率。

比如我們想要查詢商品評論表中不同的 user_id 都有哪些,如果我們沒有對 user_id 創建索引,執行 SQL 語句,看看情況是怎樣的。

SELECT DISTINCT(user_id) FROM product_comment;

如果我們對 user_id 創建索引,再執行 SQL 語句,看看情況又是怎樣的。

SELECT DISTINCT(user_id) FROM product_comment_copy1;

你能看到 SQL 查詢效率有了提升,同時顯示出來的 user_id 還是按照遞增的順序進行展示的。這是因為索引會對數據按照某種順序進行排序,所以在去重的時候也會快很多。

6. 做多表 JOIN 連接操作時,創建索引需要注意以下的原則

首先,連接表的數量盡量不要超過 3 張,因為每增加一張表就相當于增加了一次嵌套的循環,數量級增長會非常快,嚴重影響查詢的效率。

其次,對 WHERE 條件創建索引,因為 WHERE 才是對數據條件的過濾。如果在數據量非常大的情況下,沒有 WHERE 條件過濾是非常可怕的。

最后,對用于連接的字段創建索引,并且該字段在多張表中的類型必須一致。比如 user_id 在 product_comment 表和 user 表中都為 int(11) 類型,而不能一個為 int 另一個為 varchar 類型。

舉個例子,如果我們只對 user_id 創建索引,執行 SQL 語句:

SELECT comment_id, comment_text, product_comment.user_id, user_name FROM product_comment JOIN `user` ON product_comment.user_id = user.user_id WHERE comment_text = 'b553b3c594868df9fd31';

這里我們對 comment_text 創建索引,再執行上面的 SQL 語句

SELECT comment_id, comment_text, product_comment_copy5.user_id, user_name FROM product_comment_copy5 JOIN `user` ON product_comment_copy5.user_id = user.user_id WHERE comment_text = 'b553b3c594868df9fd31';

如果我們不使用 WHERE 條件查詢,而是直接采用 JOIN…ON…進行連接的話,即使使用了各種優化手段,總的運行時間也會很長。


什么時候不需要創建索引

我之前講到過索引不是萬能的,有一些情況是不需要創建索引的,這里再進行一下說明。

WHERE 條件(包括 GROUP BY、ORDER BY)里用不到的字段不需要創建索引,索引的價值是快速定位,如果起不到定位的字段通常是不需要創建索引的。舉個例子:

SELECT comment_id, product_id, comment_time FROM product_comment WHERE user_id = 41251;

因為我們是按照 user_id 來進行檢索的,所以不需要對其他字段創建索引,即使這些字段出現在 SELECT 字段中。

第二種情況是,如果表記錄太少,比如少于 1000 個,那么是不需要創建索引的。我之前講過一個 SQL 查詢的例子(第 23 篇中的 heros 數據表查詢的例子,一共 69 個英雄不用索引也很快),表記錄太少,是否創建索引對查詢效率的影響并不大。

第三種情況是,字段中如果有大量重復數據,也不用創建索引,比如性別字段。不過我們也需要根據實際情況來做判斷,這一點我在之前的文章里已經進行了說明,這里不再贅述。

最后一種情況是,頻繁更新的字段不一定要創建索引。因為更新數據的時候,也需要更新索引,如果索引太多,在更新索引的時候也會造成負擔,從而影響效率。

什么情況下索引失效

我們創建了索引,還要避免索引失效,你可以先思考下都有哪些情況會造成索引失效呢?下面是一些常見的索引失效的例子:

1. 如果索引進行了表達式計算,則會失效

我們可以使用 EXPLAIN 關鍵字來查看 MySQL 中一條 SQL 語句的執行計劃,比如:

SELECT comment_id, user_id, comment_text FROM product_comment WHERE comment_id+1 = 900001;
EXPLAIN SELECT comment_id, user_id, comment_text FROM product_comment_copy6 WHERE comment_id+1 = 900001;

你能看到如果對索引進行了表達式計算,索引就失效了。這是因為我們需要把索引字段的取值都取出來,然后依次進行表達式的計算來進行條件判斷,因此采用的就是全表掃描的方式,運行時間也會慢很多。

為了避免索引失效,我們對 SQL 進行重寫:

SELECT comment_id, user_id, comment_text FROM product_comment_copy6 WHERE comment_id = 900000;

2. 如果對索引使用函數,也會造成失效

比如我們想要對 comment_text 的前三位為 abc 的內容進行條件篩選,這里我們來查看下執行計劃:

EXPLAIN SELECT comment_id, user_id, comment_text FROM product_comment WHERE SUBSTRING(comment_text, 1,3)='abc';

你能看到對索引字段進行函數操作,造成了索引失效,這時可以進行查詢重寫:

EXPLAIN SELECT comment_id, user_id, comment_text FROM product_comment WHERE comment_text LIKE 'abc%'

你能看到經過查詢重寫后,可以使用索引進行范圍檢索,從而提升查詢效率。

3. 在 WHERE 子句中,如果在 OR 前的條件列進行了索引,而在 OR 后的條件列沒有進行索引,那么索引會失效。

比如下面的 SQL 語句,comment_id 是主鍵,而 comment_text 沒有進行索引,因為 OR 的含義就是兩個只要滿足一個即可,因此只有一個條件列進行了索引是沒有意義的,只要有條件列沒有進行索引,就會進行全表掃描,因此索引的條件列也會失效:

EXPLAIN SELECT comment_id, user_id, comment_text FROM product_comment WHERE comment_id = 900001 OR comment_text = '462eed7ac6e791292a79';

如果我們把 comment_text 創建了索引會是怎樣的呢?

你能看到這里使用到了 index merge,簡單來說 index merge 就是對 comment_id 和 comment_text 分別進行了掃描,然后將這兩個結果集進行了合并。這樣做的好處就是避免了全表掃描。

4. 當我們使用 LIKE 進行模糊查詢的時候,后面不能是 %

EXPLAIN SELECT comment_id, user_id, comment_text FROM product_comment_copy5 WHERE comment_text LIKE '%abc';

這個很好理解,如果一本字典按照字母順序進行排序,我們會從首位開始進行匹配,而不會對中間位置進行匹配,否則索引就失效了。

5. 索引列與 NULL 或者 NOT NULL 進行判斷的時候也會失效。

這是因為索引并不存儲空值,所以最好在設計數據表的時候就將字段設置為 NOT NULL 約束,比如你可以將 INT 類型的字段,默認值設置為 0。將字符類型的默認值設置為空字符串 (’’)。

6. 我們在使用聯合索引的時候要注意最左原則

最左原則也就是需要從左到右的使用索引中的字段,一條 SQL 語句可以只使用聯合索引的一部分,但是需要從最左側開始,否則就會失效。我在講聯合索引的時候舉過索引失效的例子。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容