1. 程式人生 > >資料庫表索引利與弊

資料庫表索引利與弊

索引的利弊與如何判定,是否需要索引

相信讀者都知道索引能夠極大地提高資料檢索的效率,讓Query 執行得更快,但是可能並不是每一位朋友都清楚索引在極大提高檢索效率的同時,也給資料庫帶來了一些負面的影響。下面就分別對 MySQL 中索引的利與弊做一個簡單的分析。

索引的好處

索引帶來的益處可能很多讀者會認為只是"能夠提高資料檢索的效率,降低資料庫的IO成本"。

確實,在資料庫中表的某個欄位建立索引,所帶來的最大益處就是將該欄位作為檢索條件時可以極大地提高檢索效率,加快檢索時間,降低檢索過程中須要讀取的資料量。但是索引帶來的收益只是提高表資料的檢索效率嗎?當然不是,索引還有一個非常重要的用途,那就是降低資料的排序成本。

我們知道,每個索引中的資料都是按照索引鍵鍵值進行排序後存放的,所以,當Query 語句中包含排序分組操作時,如果排序欄位和索引鍵欄位剛好一致,MySQL Query Optimizer 就會告訴 mysqld 在取得資料後不用排序了,因為根據索引取得的資料已經滿足客戶的排序要求。

那如果是分組操作呢?分組操作沒辦法直接利用索引完成。但是分組操作是須要先進行排序然後分組的,所以當Query 語句中包含分組操作,而且分組欄位也剛好和索引鍵欄位一致,那麼mysqld 同樣可以利用索引已經排好序的這個特性,省略掉分組中的排序操作。

排序分組操作主要消耗的是記憶體和 CPU 資源,如果能夠在進行排序分組操作中利用好索引,將會極大地降低CPU資源的消耗。

索引的弊端

索引的益處已經清楚了,但是我們不能只看到這些益處,並認為索引是解決 Query 優化的聖經,只要發現 Query 執行不夠快就將 WHERE 子句中的條件全部放在索引中。

確實,索引能夠極大地提高資料檢索效率,也能夠改善排序分組操作的效能,但有不能忽略的一個問題就是索引是完全獨立於基礎資料之外的一部分資料。假設在Table ta 中的Column ca 建立了索引 idx_ta_ca,那麼任何更新 Column ca 的操作,MySQL在更新表中 Column ca的同時,都須要更新Column ca 的索引資料,調整因為更新帶來鍵值變化的索引資訊。而如果沒有對 Column ca 進行索引,MySQL要做的僅僅是更新表中 Column ca 的資訊。這樣,最明顯的資源消耗就是增加了更新所帶來的 IO 量和調整索引所致的計算量。此外,Column ca 的索引idx_ta_ca須要佔用儲存空間,而且隨著 Table ta 資料量的增加,idx_ta_ca 所佔用的空間也會不斷增加,所以索引還會帶來儲存空間資源消耗的增加。

如何判定是否須要建立索引

在瞭解了索引的利與弊之後,那我們到底該如何來判斷某個索引是否應該建立呢?

實際上,並沒有一個非常明確的定律可以清晰地定義什麼欄位應該建立索引,什麼欄位不該建立索引。因為應用場景實在是太複雜,存在太多的差異。當然,還是仍然能夠找到幾點基本的判定策略來幫助分析的。

1. 較頻繁的作為查詢條件的欄位應該建立索引

提高資料查詢檢索的效率最有效的辦法就是減少須要訪問的資料量,從上面索引的益處中我們知道,索引正是減少通過索引鍵欄位作為查詢條件的 Query 的IO量之最有效手段。所以一般來說應該為較為頻繁的查詢條件欄位建立索引。

2. 唯一性太差的欄位不適合單獨建立索引,即使頻繁作為查詢條件

唯一性太差的欄位主要是指哪些呢?如狀態欄位、型別欄位等這些欄位中存放的資料可能總共就是那麼幾個或幾十個值重複使用,每個值都會存在於成千上萬或更多的記錄中。對於這類欄位,完全沒有必要建立單獨的索引。因為即使建立了索引,MySQL Query Optimizer 大多數時候也不會去選擇使用,如果什麼時候 MySQL Query Optimizer選擇了這種索引,那麼非常遺憾地告訴你,這可能會帶來極大的效能問題。由於索引欄位中每個值都含有大量的記錄,那麼儲存引擎在根據索引訪問資料的時候會帶來大量的隨機IO,甚至有些時候還會出現大量的重複IO。

這主要是由於資料基於索引掃描的特點引起的。當我們通過索引訪問表中資料時,MySQL 會按照索引鍵的鍵值順序來依序訪問。一般來說,每個資料頁中大都會存放多條記錄,但是這些記錄可能大多數都不會和你所使用的索引鍵的鍵值順序一致。

假如有以下場景,我們通過索引查詢鍵值為A和B的某些資料。在通過A鍵值找到第一條滿足要求的記錄後,會讀取這條記錄所在的 X 資料頁,然後繼續往下查詢索引,發現 A 鍵值所對應的另外一條記錄也滿足要求,但是這條記錄不在 X 資料頁上,而在Y資料頁上,這時候儲存引擎就會丟棄X資料頁,而讀取Y資料頁。如此繼續一直到查詢完A鍵值所對應的所有記錄。然後輪到B鍵值了,這時發現正在查詢的記錄又在X資料頁上,可之前讀取的 X 資料頁已經被丟棄了,只能再次讀取 X 資料頁。這時候,實際上已經重複讀取 X 資料頁兩次了。在繼續往後的查詢中,可能還會出現一次又一次的重複讀取,這無疑給儲存引擎極大地增加了IO訪問量。

不僅如此,如果一個鍵值對應了太多的資料記錄,也就是說通過該鍵值會返回佔整個表比例很大的記錄時,由於根據索引掃描產生的都是隨機 IO,其效率比進行全表掃描的順序IO效率低很多,即使不會出現重複 IO 的讀取,同樣會造成整體 IO 效能的下降。

很多比較有經驗的 Query 調優專家經常說,當一條Query返回的資料超過了全表的 15%時,就不應該再使用索引掃描來完成這個 Query 了。對於"15%"這個數字我們並不能判定是否很準確,但是至少側面證明了唯一性太差的欄位並不適合建立索引。

3. 更新非常頻繁的欄位不適合建立索引

上面在索引的弊端中已經分析過了,索引中的欄位被更新的時候,不僅要更新表中的資料,還要更新索引資料,以確保索引資訊是準確的。這個問題致使IO 訪問量較大增加,不僅僅影響了更新 Query 的響應時間,還影響了整個儲存系統的資源消耗,加大了整個儲存系統的負載。

當然,並不是存在更新的欄位就適合建立索引,從判定策略的用語上也可以看出,是"非常頻繁"的欄位。到底什麼樣的更新頻率應該算是"非常頻繁"呢?每秒?每分鐘?還是每小時呢?說實話,還真難定義。很多時候是通過比較同一時間段內被更新的次數和利用該欄位作為條件的查詢次數來判斷的,如果通過該欄位的查詢並不是很多,可能幾個小時或是更長才會執行一次,更新反而比查詢更頻繁,那這樣的欄位肯定不適合建立索引。反之,如果我們通過該欄位的查詢比較頻繁,但更新並不是特別多,比如查詢幾十次或更多才可能會產生一次更新,那我個人覺得更新所帶來的附加成本也是可以接受的。

4. 不會出現在 WHERE 子句中的欄位不該建立索引