1. 程式人生 > >4.偏頭痛楊的mysql教學系列之索引篇

4.偏頭痛楊的mysql教學系列之索引篇

前戲

索引是一個非常影響效能的元素,是提高資料庫查詢效能的常用&重要方法,
因此很多公司都喜歡問面試者關於索引的問題。
索引這塊的知識點不難(想想鎖機制吧),是必須要通關的遊戲,
理解為主,記憶為輔。

索引有助於分析與解決生產環境的問題。
重點在於單列索引與多列索引,以及最左原則,
還有什麼情況下應該建索引,什麼情況下不應該建索引。



什麼是索引

建立索引是為了加速對錶的查詢,
索引通過快速路徑訪問方法來快速定位資料,從而減少磁碟IO。

可以把索引理解成一本書的目錄,例如一本英文字典,我要尋找warcraft這個單詞,我首先會通過目錄找到w字母的頁,然後再去找w字母后面的a字母,
而不是從第一頁開始翻,一直翻到warcraft這頁。

從第一頁開始翻的這種類似於全表掃描,效能非常low,就算你一個很簡單的表結構,
每次單表查詢都會檢索幾十萬條資料,也是一個性能災難,
高併發場景下會造成cpu飆高、會話超時等一系列問題。

索引用來快速地尋找那些具有特定值的記錄。
如果沒有索引,執行查詢時MySQL必須從第一個記錄開始掃描整個表的所有記錄,
直至找到符合要求的記錄。表裡面的記錄數量越多,這個操作的代價就越高。
如果作為搜尋條件的列上已經建立了索引,
MySQL無需掃描任何記錄即可迅速得到目標記錄所在的位置。

在沒有給a列建立索引時,a列沒有明確的次序,給a列建索引後,
mysql將在索引中排序a列。

mysql內部儲存一個數據檔案記錄實際記錄所在位置的"指標"(方便索引快速回查到原記錄)。

我們按a列進行where條件查詢,mysql能夠在a列的索引中非常快速找到值,
然後直接轉到資料檔案中相應的行,將該行返回。

在這個過程中,mysql只需要處理一個行就可以,否則將掃描整個表的所有記錄。

個人建議不要剛上來就自己建立索引,而是資料量到達一定規模後,
通過分析工具來檢測哪些列需要真正意義上的需要建立索引。

mysql使用information_schema庫裡的STATISTICS來儲存索引資訊。



建立&刪除索引的方式

建立&刪除索引的方式分為兩種:

自動

當在表中定義主鍵約束、唯一約束、外來鍵約束時,
系統會自動建立相應的索引。資料表被刪除時,表上的索引自動被刪除。

手動

通過create index語句來建立索引。通過drop index語句來刪除索引。
(DROP INDEX 索引名 ON 表名)



索引的型別

普通索引(normal)

這是最基本的索引型別,而且它沒有唯一性之類的限制。

#建立普通索引
CREATE INDEX <索引的名字> ON tablename (列的列表);

#修改普通索引
ALTER TABLE tablename ADD INDEX [索引的名字] (列的列表);

#建立表時指定
CREATE TABLE tablename ( [...], INDEX [索引的名字] (列的列表) );

唯一索引(unique)

這種索引和前面的“普通索引”基本相同,但有一個區別:索引列的所有值都只能出現一次,即必須唯一,允許有空值。

#建立唯一索引
CREATE UNIQUE INDEX <索引的名字> ON tablename (列的列表);

#修改唯一索引
ALTER TABLE tablename ADD UNIQUE [索引的名字] (列的列表);

#建立表時指定
CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (列的列表) );

主鍵索引(primary key)

主鍵是一種唯一索引,每個表只能有一個主鍵,不允許重複,不允許為空。

#建立主鍵索引
CREATE TABLE tablename ( [...], PRIMARY KEY (列的列表) );

#修改主鍵索引
ALTER TABLE tablename ADD PRIMARY KEY (列的列表);

全文索引(full text)

全文索引可以在VARCHAR或者TEXT型別的列上建立。
全文索引不展開,可搜尋其他文章。
不建議建立全文索引,如果有搜尋場景請使用elastic search等開源框架。



單列索引與多列索引

索引可以建立在單列上,也可以建立在多列上。
假設有這樣一個people表:

這個資料片段中有四個名字為“Mikes”的人(其中兩個姓Sullivans,兩個姓McConnells),有兩個年齡為17歲的人,還有一個名字與眾不同的Joe Smith。

這個表的主要用途是根據指定的使用者姓、名以及年齡返回相應的peopleid。例如,我們可能需要查詢姓名為Mike Sullivan、年齡17歲使用者的peopleid(SQL命令為SELECT peopleid FROM people WHERE firstname=‘Mike’ AND lastname=‘Sullivan’ AND age=17;)。
由於我們不想讓MySQL每次執行查詢就去掃描整個表,這裡需要考慮運用索引。

首先,我們可以考慮在單個列上建立索引,比如firstname、lastname或者age列。
如果我們建立firstname列的索引(ALTER TABLE people ADD INDEX firstname (firstname);),
MySQL將通過這個索引迅速把搜尋範圍限制到那些firstname='Mike’的記錄,
然後再在這個“中間結果集”上進行其他條件的搜尋:它首先排除那些lastname不等於“Sullivan”的記錄,
然後排除那些age不等於17的記錄。當記錄滿足所有搜尋條件之後,MySQL就返回最終的搜尋結果。

由於建立了firstname列的索引,與執行表的完全掃描相比,MySQL的效率提高了很多,但我們要求MySQL掃描的記錄數量仍舊遠遠超過了實際所需要的。雖然我們可以刪除firstname列上的索引,再建立lastname或者age列的索引,但總地看來,不論在哪個列上建立索引搜尋效率仍舊相似。

為了提高搜尋效率,我們需要考慮運用多列索引。如果為firstname、lastname和age這三個列建立一個多列索引,MySQL只需一次檢索就能夠找出正確的結果!下面是建立這個多列索引的SQL命令:

ALTER TABLE people ADD INDEX fname_lname_age (firstname,lastname,age);

由於索引檔案以B-樹格式儲存,MySQL能夠立即轉到合適的firstname,然後再轉到合適的lastname,最後轉到合適的age。在沒有掃描資料檔案任何一個記錄的情況下,MySQL就正確地找出了搜尋的目標記錄!

那麼,如果在firstname、lastname、age這三個列上分別建立單列索引,
效果是否和建立一個firstname、lastname、age的多列索引一樣呢?答案是否定的,兩者完全不同。
當我們執行查詢的時候,MySQL只能使用一個索引。如果你有三個單列的索引,
MySQL會試圖選擇一個限制最嚴格的索引。但是,即使是限制最嚴格的單列索引,
它的限制能力也肯定遠遠低於firstname、lastname、age這三個列上的多列索引。

最左原則

多列索引還有另外一個優點,它通過稱為最左字首(Leftmost Prefixing)的概念體現出來。
現在我們有一個firstname、lastname、age列上的多列索引,我們稱這個索引為fname_lname_age。它相當於我們建立了(firstname,lastname,age)、(firstname,lastname)以及(firstname)這些列組合上的索引。

#下面這些查詢都能夠使用fname_lname_age索引:
SELECT peopleid FROM people WHERE firstname='Mike' AND lastname='Sullivan' AND age='17';
SELECT peopleid FROM people WHERE firstname='Mike' AND lastname='Sullivan';
SELECT peopleid FROM people WHERE firstname='Mike';

#下面這些查詢都不能夠使用fname_lname_age索引 :
SELECT peopleid FROM people WHERE lastname='Sullivan';
SELECT peopleid FROM people WHERE age='17';
SELECT peopleid FROM people WHERE lastname='Sullivan' AND age='17';
SELECT peopleid FROM people WHERE firstname='Mike' AND age='17';

換句話說,你建立的多列索引,只能從最左邊依次開始算,如果順序不對,則索引失效。



索引的缺點

1.索引要佔用磁碟空間。通常情況下,這個問題不是很突出。
但是,如果你建立每一種可能列組合的索引,索引檔案體積的增長速度將遠遠超過資料檔案。
如果你有一個很大的表,索引檔案的大小可能達到作業系統允許的最大檔案限制。

2.對於需要寫入資料的操作,比如DELETE、UPDATE以及INSERT操作,
索引會降低它們的速度。這是因為MySQL不僅要把改動資料寫入資料檔案,
而且它還要把這些改動寫入索引檔案。

(在設計和建立索引時,應確保對效能的提高程度大於在儲存空間和處理資源方面的代價。 )



對哪些列建立索引

一般說來,索引應建立在那些將用於JOIN, WHERE和ORDER BY排序的欄位上。

儘量不要對資料庫中某個含有大量重複的值的欄位建立索引。

例如customerinfo中的“province”… 欄位,在這樣的欄位上建立索引將不會有什麼幫助;
相反,還有可能降低資料庫的效能。

在建有索引的欄位上儘量不要使用函式進行操作。

例如,在一個DATE型別的欄位上使用YEAE()函式時,將會使索引不能發揮應有的作用。
所以,下面的兩個查詢雖然返回的結果一樣,但後者要比前者快得多。

SELECT * FROM order WHERE YEAR(OrderDate) < 2001;
SELECT * FROM order WHERE OrderDate<"2001-01-01";(快的多)
在Join表的時候使用相同型別的例,並將其索引。

如果你的應用程式有很多 JOIN 查詢,你應該確認兩個表中Join的欄位是被建過索引的。這樣,MySQL內部會啟動為你優化Join的SQL語句的機制。而且,這些被用來Join的欄位,應該是相同的型別的。例如:如果你要把 DECIMAL 欄位和一個 INT 欄位Join在一起,MySQL就無法使用它們的索引。對於那些STRING型別,還需要有相同的字符集才行。(兩個表的字符集有可能不一樣)

不要在小型表建立索引

因為在檢索索引資料時,時間可能比掃描整個表的時間還要長。

不要在頻繁更新的列上建立索引。

總結

索引是一個非常重要的概念,索引跟後面的innodb引擎下的鎖機制有著千絲萬縷的聯絡,
知識都是一環套一環的,並且索引用好了與用不好,差距還是蠻大的。
希望大家能把索引的功力發揮到最大。