1. 程式人生 > >MySQL中一些關於索引的知識點

MySQL中一些關於索引的知識點

什麼是索引

索引是一種資料結構,其作用就是用來提高資料查詢效率。比較常用的比喻就是將其類比為書籍的目錄。通過目錄可以精確的找到某一章節的內容所在頁。

在資料量較小的時候使用索引其實也沒有什麼意義,即使沒有索引需要一條一條遍歷資料對於計算機來說也並不需要太多時間。而一旦資料量較大,要保證我們能正常的對外提供服務,保證使用者使用體驗那麼索引就是必要的了。

索引型別

索引時一種資料結構,為了應對不同的場景會有多種實現。在MySQL中主要就是Hash索引和B+Tree。

Hash索引

hash相信大家應該都很熟悉,hash是一種key-value形式的資料結構。實現一般是陣列+連結串列的結構,通過hash函式計算出key在陣列中的位置,然後如果出現hash衝突就通過連結串列來解決(拉鍊法)。當然還有其他的解決hash衝突的方法。hash這種資料結構是很常用的,比如我們系統使用HashMap來構建熱點資料快取,存取效率很好。

hash結構存資料首先通過計算key的hash值來確定其在陣列中的位置,如果有衝突就在該陣列位置建一個連結串列。這樣很明顯有幾個問題:

  • 即使是具有相同特徵的key計算出來的位置可能相隔很遠,連續查詢效率低下。即不支援範圍查詢
  • hash索引儲存的事計算得到的hash值和行指標,而不儲存具體的行值,所以通過hash索引查詢資料需要進行兩次查詢(首先查詢行的位置,然後找到具體的資料)
  • hash索引查詢資料的前提就是計算hash值,也就是要求key為一個能準確指向一條資料的key,所以對於like等一類的匹配查詢是不支援的。

所以我們可以知道的是hash索引適用於快速選取某一行的資料。

B+Tree結構

從名字上看這明顯是一種樹結構,在大學期間資料結構的課本上樹結構是必講的。樹結構是一種特別重要的資料結構,在很多地方都會使用到。

上面我們說到hash索引無法進行範圍查詢,在樹結構中也有一種方便進行有序查詢的結構--二叉搜尋樹。二叉搜尋樹的結構中要求父節點的值大於左孩子節點並且小於右孩子節點,如下圖。

上圖中二叉樹的查詢的時間複雜度為O(log(n)),當然要保證O(log(n))的時間複雜度就需要保證二叉樹時刻保持平衡。

而在MySQL索引中雖然也使用了樹結構,但是並不是使用的二叉樹。因為在資料庫中資料最終都是存放在磁碟上的,而如果樹的節點過多的話,那麼在節點之間轉移會花費較多的時間。在MySQL的實現中選擇將更多內容放在同一個節點,對同一個節點的操作轉入在記憶體中完成,減少在外存中節點之間轉移的次數,以達到提高效率的目的。這就是B+Tree,在B+Tree的實現中一個三層的樹結構就基本上可以滿足我們幾乎所有的需求了。

B-Tree

要了解B+Tree首先就得了解B-Tree,B-Tree是一種平衡樹,這裡的B指的是Balance而不是Binary,更確切的說B-Tree是一種多路平衡搜尋樹。

多路平衡搜尋樹如下圖:

這是一種2-3樹,意思就是每個節點存有兩個值,同時每個節點分支數為3,從上圖中可以看出來著中結構很適合查詢資料。每個節點的左子樹的值都是小於當前節點中最小的值,中間的子樹的值全都是在當前節點兩個值的中間,而右子樹的值全都大於當前節點的最大值。

比如我們要查詢24這個值:

  1. 首先從根節點判斷24在根節點(15,25)之間,所以左右子樹排除,從中間查詢。
  2. 然後找到中間子樹的根節點(18,22),比較發現24大於該節點最大值,排除左子樹和中間子樹。
  3. 找到右子樹,判斷節點大值剛好等於24,查詢結束

基於上面的流程可以總結B樹的搜尋:

  • 從根結點開始,對結點內的關鍵字(有序)序列進行二分查詢。
  • 如果命中則結束,否則進入查詢關鍵字所屬範圍的子結點;
  • 重複上面的流程,直到所對應的子節點為空,或已經是葉子結點;

可以看出其搜尋效能相當於在關鍵字集合內做一次二分查詢。從這裡看來好像B-Tree沒有什麼問題,但是需要注意到的是在B-Tree中每一個節點都是儲存索引關鍵字以及其代表的具體行資料。而在MySQL中資料庫載入資料是以頁為單位載入,每一頁的大小是固定的(預設16k)。如果每一個節點都儲存所有的值,那麼一頁中能存下的節點就會很少,一次查詢可能就會進行多次從記憶體中去載入資料,導致效能降低。

B+Tree

B+Tree是對B-Tree的一個變種,讓其更加適應於進行外部儲存檔案索引。

兩者之前最大的不同就在於B-Tree的每個節點都儲存所有的資料,而B+Tree需要儲存的資料都在葉子節點上,並且增加了順序訪問指標,每個葉子節點都有指向下一個相鄰的葉子節點的地址。這樣的結構保證了在一個記憶體頁中可以存下更多的索引節點,並且更加適合進行範圍查詢。

索引

因為儲存引擎負責實現索引,所以接下來討論索引都是基於MySQL的InnoDB引擎。

聚簇索引

聚簇的意思是表示資料行和相鄰的鍵值聚簇的儲存在一起。一些資料庫允許選擇具體的某一個索引作為聚簇索引,而在InnoDB的實現中直接將主鍵索引指定為聚簇索引。如果沒有定義主鍵,InnoDB 會選擇一個唯一的非空索引來代替主鍵索引。如果同樣沒有定義這樣的索引,InnoDB會隱式定義一個主鍵來作為聚簇索引(row_id)。

聚簇索引例項如圖:

非聚簇索引索引

在InnoDB中除主鍵索引外其他都是非聚簇索引,所以也叫非主鍵索引。非主鍵索引的葉節點並不是儲存一行的值,而是儲存具體行的主鍵值。不滿足聚簇的定義。

非聚簇索引例項如圖:

聚簇索引和非聚簇索引在查詢時的差異

由上面的兩種索引例項圖就可以看出來,在查詢時如果是通過主鍵索引查詢的話直接查詢到資料行然後返回。但是如果是通過非主鍵索引查詢的話首先需要通過該索引確定主鍵,然後通過得到的主鍵從主鍵索引中查到具體行的資料,後面的通過得到的主鍵從主鍵索引中獲取資料的過程被稱為回表。

回表的過程使得通過普通索引查詢較主鍵索引查詢多了一步,在很多情況下效率相對較低。所以在我們的查詢過程中如果能夠僅通過主鍵確定資料那最好就是直接使用主鍵進行查詢。

覆蓋索引

上面介紹了通過非主鍵查詢會有一個回表的過程,但是需要注意的是並不是每一個查詢都存在回表這一步,對於一個普通索引來說其葉節點儲存的是主鍵的值,那麼假設我現在需要的資料也僅僅就是主鍵的值呢?通過普通索引取到主鍵的值後就並不需要再到主鍵索引中查,那麼也就不存在回表這一過程了。

上面例子中該非主鍵索引已經存在了我們所需要的值,所以該索引也被稱為覆蓋索引。覆蓋索引並不是一個固定的結構,可以使單索引(一個欄位的索引),也可以使複合索引,凡是能夠直接提供查詢結果而不需要進行回表過程的都可以被稱為覆蓋索引。

很多時候我們不可能僅僅通過主鍵來確定資料,使用普通索引可能會導致低效,所以覆蓋索引在日常開發過程中也是一個很常用的效能優化的手段。

當然覆蓋索引頁並不都是好的,比如我現在建立了一個索引index(a,b)。由a,b兩個欄位來建立索引,好處已經說過了就是查詢ab欄位時不會回表,但是如果僅僅通過b欄位來查詢就無法走這個索引了。建立的索引的索引項是按照索引定義裡面出現的欄位順序排序的。

最左字首原則

假設現在存在索引index(a,b),那麼如果通過a和b來查詢能夠應用該索引,單獨使用a來查詢也能應用到該索引,但是如果單獨使用b來查詢則無法應用到該索引。這就是最左字首原則,在匹配索引時回匹配索引最左邊的n個欄位,能匹配上就可以應用該索引。

由於最左字首原則的存在也就要求我們在建立索引時可能需要考慮更多的事情。

首先需要清楚的事索引是一種資料結構,建立索引時需要消耗儲存空間的,所以索引並不是建立的越多越好,而是應該根據需求儘可能的減少索引的數量。

而最左字首原則的存在就使得一個聯合索引可以被當成多個索引來使用,當然前提是設計好索引中欄位的順序(實際上最左字首原則也並不是僅僅適用於聯合索引,對於字串索引也使用,字串索引中最左n個字元相當於聯合索引中的最左n個欄位)。

比如index(a,b),有了這個索引後我們就不需要單獨為a建立索引,所以在設計聯合索引時一般將使用頻率較高的欄位放在前面。

然後是將區分度較高的欄位靠前,區分度就是欄位中值的重複率,重複率越低區分度越高。比如性別就不適合作為索引,區分度越高的欄位經過一次篩選能過濾掉更多的行。

然後還需要考慮的是欄位的大小,由於索引也需要佔據空間所以一般選用較小的欄位。

參考資料

MySQL運維內參:MySQL、Galera、Inception核心原理與最佳實