爲什麼要使用索引?

前言

  爲什麼要使用索引?影響數據庫效率的原因千千萬萬,使用索引是爲了解決哪方面的數據庫的瓶頸?如果我們到現在只知道使用索引能夠查詢更快、索引就像目錄,我們根據目錄再進行查詢就很快,等等這些東西,那我們還有很長的路要走。

點一

  MySQL 數據庫存儲數據最終是以文件的形式存儲到硬盤的。一般來說,我們在程序中使用的時候肯定要把磁盤文件中的數據讀到內存中。那麼就這個 “讀” 的過程是什麼樣子的呢?磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間可以分爲尋道時間、旋轉延遲、傳輸時間三個部分尋道時間指的是磁臂移動到指定磁道所需要的時間主流磁盤的尋道時間一般在5ms以下;旋轉延遲就是我們經常聽說的磁盤轉速比如一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms旋轉延遲等於磁盤轉動半圈時間);傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,一般在零點幾毫秒,相對於前兩個時間可以忽略不計。那麼訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 - MIPS 的機器每秒可以執行5億條指令,因爲指令依靠的是電的性質,換句話說執行一次IO的時間可以執行40萬條指令(如果以 CPU 的指令執行效率來比較的話),數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。下圖是計算機硬件延遲的對比圖,供大家參考;

圖 1-1: 計算機硬件延遲對比圖

  所以,問題的癥結就在於磁盤 IO 是非常高昂的操作。

  解決方案:

    ①:計算機操作系統做了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內因爲局部預讀性原理告訴我們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據我們稱之爲一頁(page)。具體一頁有多大數據跟操作系統有關,一般爲4k或8k,也就是我們讀取一頁內的數據時候,實際上才發生了一次IO(一次 IO 的數據包括當前要讀取的磁盤地址的數據+與之相鄰的數據),這個理論對於索引的數據結構設計非常有幫助。

    ②:每次查找數據時把磁盤IO次數控制在一個很小的數量級

  很明顯:第①種解決方案是系統已經提供好的。要想實現第二種解決方案就需要一種穩定的數據結構能夠滿足幾乎每次查詢數據進行磁盤的 IO 次數是很少的。這個條件可以解釋爲:每次查詢數據進行的 IO 次數都很少,說明這個數據結構不能像紅黑樹一樣樹的高度不可控,於是一個高度可控的多路搜索樹就產生了,這就是 B + 樹。

  在這裏要闡述清楚一件事情:那麼B + 樹的每一個節點(以磁盤的角度可以稱作:磁盤塊;參照下圖)究竟存的是什麼?以一張表的 id 列爲例,也就是主鍵列是如果是索引的話,那麼這張表的每一個 id 都會以 B+ 樹的每一個節點存儲到 B+ 樹上,如果數據過多,一個節點就會存儲多個 id(這裏只是形象的認識,下面有詳細解釋)。

B + 樹

圖 1-2: B+樹數據結構圖

  如上圖,是一顆 B + 樹,關於 B + 樹的定義可以參見 B + 樹,這裏只說一些重點,淺藍色的塊我們稱之爲一個磁盤塊,可以看到每個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),如磁盤塊1包含數據項17和35(如果以 id 爲例的話,就代表一個節點(磁盤塊)存放了兩個 id),包含指針P1、P2、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。真實的數據存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節點不存儲真實的數據,只存儲指引搜索方向的數據項(也就是說可以理解爲是17、35的一個縮影),17、35並不真實存在於非葉子節點中

  那麼 B + 樹的查找數據的過程又是怎樣的呢?

    如圖所示,如果要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存(並不是將所有的磁盤塊(節點)一次性都加入到內存中),此時發生一次IO(磁盤加載塊到內存的這個過程稱爲一次 IO),在內存中使用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因爲非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的數據(以 id 作爲索引的話就是幾百萬個 id 也就是幾百萬條數據),如果上百萬的數據查找只需要三次IO(每一個磁盤塊中存儲多個 id),性能提高將是巨大的,如果沒有索引,每個磁盤塊都要發生一次IO(即使磁盤塊裏面可以裝多個數據項的縮影,但是由於單個磁盤塊的大小是 4k 或者 8k,這是由於操作系統限制的,所以單個磁盤塊中不可能存儲太多數據,所以磁盤塊的數目依然很多),顯然成本非常非常高。

  B + 樹的性質
    1、通過上面的查找數據的過程,我們知道 IO 次數取決於 B + 數的高度 h,假設當前數據表的數據爲 N,每個磁盤塊的數據項的數量是 m,則有 h = ㏒(m+1) N,當數據量 N 一定的情況下,m 越大,h 越小;而 m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,如果數據項佔的空間越小,數據項的數量越多,樹的高度越低。這就是爲什麼每個數據項,即索引字段要儘量的小(雖然數據項中存儲的是索引字段的縮影,但是縮影的佔字節數還是和真實的數據有關係的),比如 int 佔4字節,要比 bigint8 字節少一半。這也是爲什麼 B + 樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度下降,導致樹增高。當數據項等於1(一個數據項佔用一個磁盤塊)時將會退化成線性表
    2、當 B 樹的數據項是複合的數據結構,比如(name,age,gender)的時候,B + 樹是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,+ 樹會優先比較 name 來確定下一步的搜索方向,如果 name 相同再依次比較 age 和 gender,最後得到檢索的數據;但當(20,F)這樣的沒有 name 的數據來的時候,+ 樹就不知道下一步該查哪個節點,因爲建立搜索樹的時候 name 就是第一個比較因子,必須要先根據 name 來搜索才能知道下一步去哪裏查詢。比如當(張三,F)這樣的數據來檢索時,+ 樹可以用 name 來指定搜索方向,但下一個字段 age 的缺失,所以只能把名字等於張三的數據都找到,然後再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章