1. 程式人生 > >B+樹在資料庫索引中的應用

B+樹在資料庫索引中的應用

目前大部分資料庫系統及檔案系統都採用B-Tree或其變種B+Tree作為索引結構(更少的磁碟I/O操作次數的漸進複雜度)

一般來說,索引本身也很大,不可能全部儲存在記憶體中,因此索引往往以索引檔案的形式儲存的磁碟上。這樣的話,索引查詢過程中就要產生磁碟I/O消耗,相對於記憶體存取,I/O存取的消耗要高几個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查詢過程中磁碟I/O操作次數的漸進複雜度。換句話說,索引的結構組織要儘量減少查詢過程中磁碟I/O的存取次數。

MyISAM的索引實現

資料庫不同的搜尋引擎輔助索引的實現原理不一樣,MyISAM是索引和檔案分離的

一般以主鍵為索引的叫做主索引,而以其他鍵為索引的叫做輔助索引;

直接上MyISAM的實現原理,利用B+樹實現,

這裡寫圖片描述

由上圖可以看出,col1是主鍵,而葉子結點儲存的資料是一個地址,通過地址找到資料;

下面是Col2列的輔助索引(和主索引不同的是輔助索引的key是可以重複的)
這裡寫圖片描述

同樣也是一顆B+Tree,data域儲存資料記錄的地址。因此,MyISAM中索引檢索的演算法為首先按照B+Tree搜尋演算法搜尋索引,如果指定的Key存在,則取出其data域的值,然後以data域的值為地址,讀取相應資料記錄。

MyISAM的索引方式也叫做“非聚集”的,之所以這麼稱呼是為了與InnoDB的聚集索引區分。

InnoDB的索引實現

雖然InnoDB也使用B+Tree作為索引結構,但具體實現方式卻與MyISAM截然不同。

第一個重大區別是InnoDB的資料檔案本身就是索引檔案。從前文知道,MyISAM索引檔案和資料檔案是分離的,索引檔案僅儲存資料記錄的地址。而在InnoDB中,表資料檔案本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域儲存了完整的資料記錄。這個索引的key是資料表的主鍵,因此InnoDB表資料檔案本身就是主索引。

這裡寫圖片描述

上圖是InnoDB主鍵索引(同時也是資料檔案)的示意圖,可以看到葉節點包含了完整的資料記錄。這種索引叫做聚集索引。因為InnoDB的資料檔案本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識資料記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含欄位作為主鍵,這個欄位長度為6個位元組,型別為長整形。

注意,和MyISAM不同的是葉子結點的資料域儲存的是全部資料;

第二個與MyISAM索引的不同是InnoDB的輔助索引data域儲存相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。例如,下圖為定義在Col3上的一個輔助索引:

這裡寫圖片描述

聚集索引這種實現方式使得按主鍵的搜尋十分高效,但是輔助索引搜尋需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

瞭解不同儲存引擎的索引實現方式對於正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實現後,就很容易明白為什麼不建議使用過長的欄位作為主鍵,因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的欄位作為主鍵在InnoDB中不是個好主意,因為InnoDB資料檔案本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時資料檔案為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增欄位作為主鍵則是一個很好的選擇。

MyISAM和InnoDB在索引上的優劣

既然MyISAM和InnoDB是MySQL的兩代引擎,肯定會有一個提升,而InnoDB是最新一代,那麼它到底優在哪裡?

試想,MyISAM和InnoDB都是以B+樹為基礎實現的,相對於B樹的不同其實前面已經講過,即資料域和結點分離;

而MyISAM更是索引和檔案分離,B+樹的葉子結點的資料域存放的是檔案內容的地址,主索引和輔助索引的B+樹都是如此,那麼如果我改變了一個地址,是不是所有的索引樹都得改變,正如前面我們講的在磁碟上頻繁的讀寫操作是效率很低的,而這塊又不適用區域性原理,因為邏輯上相鄰的結點,物理上不一定相鄰,那麼這樣就會造成效率上的降低;

於是乎,InnoDB就產生了,它讓除了主索引以外的輔助索引的葉子結點的資料域都儲存主鍵,先通過輔助索引找到主鍵,然後通過主鍵找到葉子結點的所有資料,聽起來貌似很麻煩,遍歷了兩棵樹,但是,這樣如果有了修改的話,改變的只是主索引,其它輔助索引都不用動,而且,資料庫中的樹的每一個結點的key可不是咱們給的那麼少,試想如果一個結點有1024個key,那麼高度為2的B+樹都有1024*1024個key,所以一般樹的高度都很低,所以,遍歷樹的消耗幾乎忽略不計!

InnoDB的主鍵選擇與插入優化

在使用InnoDB儲存引擎時,如果沒有特別的需要,請永遠使用一個與業務無關的自增欄位作為主鍵。

經常看到有帖子或部落格討論主鍵選擇問題,有人建議使用業務無關的自增主鍵,有人覺得沒有必要,完全可以使用如學號或身份證號這種唯一欄位作為主鍵。不論支援哪種論點,大多數論據都是業務層面的。如果從資料庫索引優化角度看,使用InnoDB引擎而不使用自增主鍵絕對是一個糟糕的主意。

上文討論過InnoDB的索引實現,InnoDB使用聚集索引,資料記錄本身被存於主索引(一顆B+Tree)的葉子節點上。這就要求同一個葉子節點內(大小為一個記憶體頁或磁碟頁)的各條資料記錄按主鍵順序存放,因此每當有一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,如果頁面達到裝載因子(InnoDB預設為15/16),則開闢一個新的頁(節點)。

如果表使用自增主鍵,那麼每次插入新的記錄,記錄就會順序新增到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁。

這樣就會形成一個緊湊的索引結構,近似順序填滿。由於每次插入時也不需要移動已有資料,因此效率很高,也不會增加很多開銷在維護索引上。

如果使用非自增主鍵(如果身份證號或學號等),由於每次插入主鍵的值近似於隨機,因此每次新紀錄都要被插到現有索引頁得中間某個位置。

此時MySQL不得不為了將新記錄插到合適位置而移動資料,甚至目標頁面可能已經被回寫到磁碟上而從快取中清掉,此時又要從磁碟上讀回來,這增加了很多開銷,同時頻繁的移動、分頁操作造成了大量的碎片,得到了不夠緊湊的索引結構,後續不得不通過OPTIMIZE TABLE來重建表並優化填充頁面。

因此,只要可以,請儘量在InnoDB上採用自增欄位做主鍵。