索引的各種規則紛繁複雜,不瞭解索引的組織形式就沒辦法真正地理解資料庫索引。通過本文,你可以深入地理解資料庫索引在資料庫中究竟是如何組織的,從此以後索引的規則對於你將變得清清楚楚、明明白白,再也不需要死記硬背。
順暢地閱讀這篇文章需要了解索引、聯合索引、聚集索引分別都是什麼,如果你還不瞭解,可以通過另一篇文章來輕鬆理解——資料庫索引是什麼?新華字典來幫你。
這篇文章是一系列資料庫索引文章中的第二篇,這個系列包括了下面四篇文章:
-
資料庫索引是什麼?新華字典來幫你 —— 理解
-
資料庫索引融會貫通 —— 深入
-
20分鐘資料庫索引設計實戰 —— 實戰
-
資料庫索引為什麼用B+樹實現? —— 擴充套件
這一系列涵蓋了資料庫索引從理論到實踐的一系列知識,一站式解決了從理解到融會貫通的全過程,相信每一篇文章都可以給你帶來更深入的體驗。
索引的組織形式
通過之前的內容,我們已經對資料庫索引有了相當程度的抽象瞭解,那麼在資料庫中,索引實際是以什麼樣的形式進行組織的呢?同一張表上的多個索引又是怎樣分工合作的呢?
目前絕大多數情況下使用的資料庫索引都是使用B+樹實現的,下面就以MySQL的InnoDB為例,介紹一下資料庫索引的具體實現。
聚集索引
下面是一個以B+樹形式組織的拼音索引,在B+樹中,每一個節點裡都有N個按順序排列的值,且每個值的中間和節點的頭尾都有指向下一級節點的指標。在查詢過程中,按順序從頭到尾遍歷一個節點中的值,當發現要找的目標值恰好在一個指標的前一個值之後、後一個值之前時,就通過這個指標進入下一級節點。當最後到達葉子節點,也就是最下層的節點時,就能夠找到自己希望查詢的資料記錄了。
在上圖中如果希望找到險
字,那麼我們首先通過拼音首字母在根節點上按順序查詢到了X
和Y
之間的指標,然後通過這個指標進入了第二級節點···, xia, xian, xiang, ···
。之後在該節點上找到了xian
和xiang
之間的指標,這樣就定位到了第519頁開始的一個目標資料塊,其中就包含了我們想要找到的險
字。
因為拼音索引是聚集索引,所以我們在葉子節點上直接就找到了我們想找的資料。
非聚集索引
下面是一個模擬部首索引的組織形式。我們由根節點逐級往下查詢,但是在最後的葉子節點上並沒有找到我們想找的資料,那麼在使用這個索引時我們是如何得到最終的結果的呢?回憶之前字典中“檢字表”的內容,我們可以看到,在每個字邊上都有一個頁碼,這就相當於下面這一個索引中葉子節點上險
字與院
字中間的指標,這個指標會告訴我們真正的資料在什麼地方。
下圖中,我們把非聚集索引(部首索引)和聚集索引(拼音索引)合在一起就能看出非聚集索引最後到底如何查詢到實際資料了。非聚集索引葉子節點上的指標會直接指向聚集索引的葉子節點,因為根據聚集索引的定義,所有資料都是按聚集索引組織儲存的,所以所有實際資料都儲存在聚集索引的葉子節點中。而從非聚集索引的葉子節點連結到聚集索引的葉子節點查詢實際資料的過程就叫做——回表。
全覆蓋索引
那麼如果我們只是想要驗證險
字的偏旁是否是雙耳旁“阝”
呢?這種情況下,我們只要在部首索引中阝
下游的葉子節點中找到了險
字就足夠了。這種在索引中就獲取到了SQL語句中需要的所有欄位,所以不需要再回表查詢的情況中,這個索引就被稱為這個SQL語句的全覆蓋索引。
在實際的資料庫中,非聚集索引的葉子節點上儲存的“指標”就是聚集索引中所有欄位的值,要獲取一條實際資料,就需要通過這幾個聚集索引欄位的值重新在聚集索引上執行一遍查詢操作。如果資料量不多,這個開銷是非常小的;但如果非聚集索引的查詢結果中包含了大量資料,那麼就會導致回表的開銷非常大,甚至超過不走索引的成本。所以全覆蓋索引可以節約回表的開銷這一點在一些回表開銷很大的情況下就非常重要了。
範圍查詢條件
上圖是一個聯合索引idx_eg(col_a, col_b)
的結構,如果我們希望查詢一條滿足條件col_a = 64 and col_b = 128
的記錄,那麼我們可以一路確定地往下找到唯一的下級節點最終找到實際資料。這種情況下,索引上的col_a
和col_b
兩個欄位都能被使用。
但是如果我們將查詢條件改為範圍查詢col_a > 63 and col_b = 128
,那麼我們就會需要查詢所有符合條件col_a > 63
的下級節點指標,最後不得不遍歷非常多的節點及其子節點。這樣的話對於索引來說就得不償失了,所以在這種情況下,資料庫會選擇直接遍歷所有滿足條件col_a > 63
的記錄,而不再使用索引上剩下的col_b
欄位。資料庫會從第一條滿足col_a > 63
的記錄開始,橫向遍歷之後的所有記錄,從裡面排除掉所有不滿足col_b = 128
的記錄。
這就是範圍條件會終止使用聯合索引上的後續欄位的原因。