B+樹索引

https://www.iteye.com/blog/zhuyuehua-1872202node

 

1.索引結構  
  
    1.1 B+樹索引結構
   
    從物理上說,索引一般能夠分爲:分區和非分區索引、常規B樹索引、位圖(bitmap)索引、翻轉
(reverse)索引等。其中,B樹索引屬於最多見的索引

   B樹索引是一個典型的樹結構,其包含的組件主要是:緩存

 

      葉子節點(Leaf node):包含條目直接指向表裏的數據行。oracle

 

      分支節點(Branch node):包含的條目指向索引裏其餘的分支節點或者是葉子節點。性能

 

      根節點(Root node):一個B樹索引只有一個根節點,它實際就是位於樹的最頂端分支節點。3d

 

   能夠用下圖一來描述B樹索引的結構。其中,B表示分支節點,而L表示葉子節點。blog

          

 

     對於分支節點塊(包括根節點塊)來講,其所包含的索引條目都是按照順序排列的(缺省是升序排列,排序

 

也能夠在建立索引時指定爲降序排列)。每一個索引條目(也能夠叫作每條記錄)都具備兩個字段。第一個字遞歸

 

段表示當前該分支節點塊下面所連接的索引塊中所包含的最小鍵值;第二個字段爲四個字節,表示所連接的索引

 

索引塊的地址,該地址指向下面一個索引塊。內存

 

     在一個分支節點塊中所能容納的記錄行數由數據塊大小以及索引鍵值的長度決定。

 

     對於葉子節點塊來講,其所包含的索引條目與分支節點同樣,都是按照順序排列的(缺省是升序排列,

 

也能夠在建立索引時指定爲降序排列)。每一個索引條目(也能夠叫作每條記錄)也具備兩個字段。第一個字

 

段表示索引的鍵值,對於單列索引來講是一個值;而對於多列索引來講則是多個值組合在一塊兒的。第二個字

 

段表示鍵值所對應的記錄行的ROWID,該ROWID是記錄行在表裏的物理地址。若是索引是建立在非分區表上或

 

者索引是分區表上的本地索引的話,則該ROWID佔用6個字節;若是索引是建立在分區表上的全局索引的話,

 

則該ROWID佔用10個字節。

 

     1.2 估算索引條目

 

對於每一個索引塊來講,缺省的PCTFREE爲10%,也就是說最多隻能使用其中的90%。同時9i後,這90%

 

中也不可能用盡,只能使用其中的87%左右。也就是說,8KB的數據塊中可以實際用來存放索引數據的

 

空間大約爲6488(8192×90%×88%)個字節。

 

假設咱們有一個非分區表,表名爲warecountd,其數據行數爲130萬行。該表中有一個列,列名爲goodid,其

 

類型爲char(8),那麼也就是說該goodid的長度爲固定值:8。同時在該列上建立了一個B樹索引。

 

在葉子節點中,每一個索引條目都會在數據塊中佔一行空間。每一行用2到3個字節做爲行頭,行頭用來存放標

 

記以及鎖定類型等信息。同時,在第一個表示索引的鍵值的字段中,每個索引列都有1個字節表示數據長

 

度,後面則是該列具體的值。那麼對於本例來講,在葉子節點中的一行所包含的數據大體以下圖二所示:

    

 

從上圖能夠看到,在本例的葉子節點中,一個索引條目佔18個字節。同時咱們知道8KB的數據塊中真正能夠用

 

來存放索引條目的空間爲6488字節,那麼在本例中,一個數據塊中大約能夠放360(6488/18)個索引條目。

 

而對於咱們表中的130萬條記錄來講,則須要大約3611(1300000/360)個葉子節點塊。

 

而對於分支節點裏的一個條目(一行)來講,因爲它只需保存所連接的其餘索引塊的地址便可,而不須要保

 

存具體的數據行在哪裏,所以它所佔用的空間要比葉子節點要少。分支節點的一行中所存放的所連接的最小

 

鍵值所需空間與上面所描述的葉子節點相同;而存放的索引塊的地址只須要4個字節,比葉子節點中所存放

 

的ROWID少了2個字節,少的這2個字節也就是ROWID中用來描述在數據塊中的行號所需的空間。所以,本例中

 

在分支節點中的一行所包含的數據大體以下圖三所示:

 

 

從上圖能夠看到,在本例的分支節點中,一個索引條目佔16個字節。根據上面葉子節點相同的方式,咱們可

 

以知道一個分支索引塊能夠存放大約405(6488/16)個索引條目。而對於咱們所須要的3611個葉子節點來

 

說,則總共須要大約9個分支索引塊。

 

這樣,咱們就知道了咱們的這個索引有2層,第一層爲1個根節點,第二層爲9個分支節點,而葉子節點數

 

爲3611個,所指向的表的行數爲1300000行。可是要注意,在oracle的索引中,層級號是倒過來的,也就是說

 

假設某個索引有N層,則根節點的層級號爲N,而根節點下一層的分支節點的層級號爲N-1,依此類推。對本例

 

來講,9個分支節點所在的層級號爲1,而根節點所在的層級號爲2。

 

2. B+樹索引的管理機制


    2.1 B+樹索引對於插入的管理

 

    對於B樹索引的插入狀況的描述,能夠分爲兩種狀況:

 

    一種是在一個已經充滿了數據的表上建立索引時,索引是怎麼管理的;

 

    另外一種則是當一行接着一行向表裏插入或更新或刪除數據時,索引是怎麼管理的。

 

對於第一種狀況來講,比較簡單。當在一個充滿了數據的表上建立索引(create index命令)時,oracle會

 

先掃描表裏的數據並對其進行排序,而後生成葉子節點。生成全部的葉子節點之後,根據葉子節點的數量生

 

成若干層級的分支節點,最後生成根節點。這個過程是很清晰的。

 

當一開始在一個空的表上建立索引的時候,該索引沒有根節點,只有一個葉子節點。

 

隨着數據不斷被插入表裏,該葉子節點中的索引條目也不斷增長,當該葉子節點充滿了索引條目而不能再放

 

下新的索引條目時,該索引就必須擴張,必須再獲取一個可用的葉子節點。這時,索引就包含了兩個葉子節

 

點,可是兩個葉子節點不可能單獨存在的,這時它們兩必須有一個上級的分支節點,其實這也就是根節點

 

了。因而,如今,咱們的索引應該具備3個索引塊,一個根節點,兩個葉子節點。

 

葉子節點的拆分過程。這個過程須要分紅兩種狀況,一種是插入的鍵值不是最大值;另外一種是插入的鍵值是最大值。

 

對於第一種狀況來講,當一個非最大鍵值要進入索引,可是發現所應進入的索引塊不足以容納當前鍵值時:

 

1)從索引可用列表上得到一個新的索引數據塊。

 

2)將當前充滿了的索引中的索引條目分紅兩部分,一部分是具備較小鍵值的,另外一部分是具備較大鍵值的。

 

Oracle會將具備較大鍵值的部分移入新的索引數據塊,而較小鍵值的部分保持不動。

 

3)將當前鍵值插入合適的索引塊中,多是原來空間不足的索引塊,也多是新的索引塊。

 

4)更新原來空間不足的索引塊的kdxlenxt信息,使其指向新的索引塊。

 

5)更新位於原來空間不足的索引塊右邊的索引塊裏的kdxleprv,使其指向新的索引塊。

 

6)向原來空間不足的索引塊的上一級的分支索引塊中添加一個索引條目,該索引條目中保存新的索引塊裏的最小鍵值,以及新的索引塊的地址。

 

從上面有關葉子節點分裂的過程能夠看出,其過程是很是複雜的。所以若是發生的是第二種狀況,則爲了簡

 

化該分裂過程,oracle省略了上面的第二步,而是直接進入第三步,將新的鍵值插入新的索引塊中。

 

 

在上例中,當葉子節點愈來愈多,致使原來的根節點不足以存放新的索引條目(這些索引條目指向葉子節

 

點)時,則該根節點必須進行分裂。當根節點進行分裂時:

 

1)從索引可用列表上得到兩個新的索引數據塊。

 

2)將根節點中的索引條目分紅兩部分,這兩部分分別放入兩個新的索引塊,從而造成兩個新的分支節點。

 

3)更新原來的根節點的索引條目,使其分別指向這兩個新的索引塊。

 

所以,這時的索引層次就變成了2層。同時能夠看出,根節點索引塊在物理上始終都是同一個索引塊。而隨着

 

數據量的不斷增長,致使分支節點又要進行分裂。分支節點的分裂過程與根節點相似(實際上根節點分裂其

 

實是分支節點分裂的一個特例而已):

 

1)從索引可用列表上得到一個新的索引數據塊。

 

2)將當前滿了的分支節點裏的索引條目分紅兩部分,較小鍵值的部分不動,而較大鍵值的部分移入新的索引塊。

3)將新的索引條目插入合適的分支索引塊。

 

4)在上層分支索引塊中添加一個新的索引條目,使其指向新加的分支索引塊。

 

當數據量再次不斷增長,致使原來的根節點不足以存放新的索引條目(這些索引條目指向分支節點)時,再

 

次引發根節點的分裂,其分裂過程與前面所說的因爲葉子節點的增長而致使的根節點分裂的過程是同樣的。

 

 

同時,根節點分裂之後,索引的層級再次遞增。由此能夠看出,根據B樹索引的分裂機制,一個B樹索引始終

 

都是平衡的。注意,這裏的平衡是指每一個葉子節點與根節點的距離都是相同的。同時,從索引的分裂機制可

 

以看出,當插入的鍵值始終都是增大的時候,索引老是向右擴展;而當插入的鍵值始終都是減少的時候,索

 

引則老是向左擴展。

 

 

圖解B+數的插入:

 

    例1

    往下圖的3階B+樹中插入關鍵字9



 

    首先查找9應插入的葉節點(最左下角的那一個),插入發現沒有破壞B+樹的性質,完畢。插完以下圖所示:

 

 

    例2

    往下圖的3階B+樹插入20



 

    首先查找20應插入的葉節點(第二個葉子節點),插入,以下圖



 

    發現第二個葉子節點已經破壞了B+樹的性質,則把之分解成[20 21], [37 44]兩個,並把21往父節點移,以下圖



 

    發現父節點也破壞了B+樹的性質,則把之再分解成[15 21], [44 59]兩個,並把21往其父節點移,以下圖



 

    此次沒有破壞B+樹的性質(若是仍是不知足B+樹的性質,能夠遞歸上去,直到知足爲至),插入完畢。

 

     3

    往下圖的3階B+樹插入100



 

首先查找100應插入的葉節點(最後一個節點), 插入,以下圖



 

修改其全部父輩節點的鍵值爲100(只有插入比當前樹的最大數大的數時要作此步),以下圖



 

而後重複Eg.2的方法拆分節點,最後得

 

 

2.2 B+樹索引對於刪除的管理

 

1)當刪除表裏的一條記錄時,其對應於索引裏的索引條目並不會被物理的刪除,只是作了一個刪除標記。

 

2)當一個新的索引條目進入一個索引葉子節點的時候,oracle會檢查該葉子節點裏是否存在被標記爲刪除的

 

索引條目,若是存在,則會將全部具備刪除標記的索引條目從該葉子節點裏物理的刪除。

 

3)當一個新的索引條目進入索引時,oracle會將當前全部被清空的葉子節點(該葉子節點中全部的索引條目

 

都被設置爲刪除標記)收回,從而再次成爲可用索引塊。

 

儘管被刪除的索引條目所佔用的空間大部分狀況下都可以被重用,但仍然存在一些狀況可能致使索引空間被

 

浪費,並形成索引數據塊不少可是索引條目不多的後果,這時該索引能夠認爲出現碎片。而致使索引出現碎

 

片的狀況主要包括:

 

1)不合理的、較高的PCTFREE。很明顯,這將致使索引塊的可用空間減小。

 

2)索引鍵值持續增長(好比採用sequence生成序列號的鍵值),同時對索引鍵值按照順序連續刪除,這時可

 

能致使索引碎片的發生。由於前面咱們知道,某個索引塊中刪除了部分的索引條目,只有當有鍵值進入該索

 

引塊時才能將空間收回。而持續增長的索引鍵值永遠只會向插入排在前面的索引塊中,所以這種索引裏的空

 

間幾乎不能收回,而只有其所含的索引條目所有刪除時,該索引塊才能被從新利用。

 

3)常常被刪除或更新的鍵值,之後幾乎再也不會被插入時,這種狀況與上面的狀況相似。

 

對於如何判斷索引是否出現碎片,方法很是簡單:直接運行ANALYZE INDEX … VALIDATE STRUCTURE命令,然

 

後檢查index_stats視圖的pct_used字段,若是該字段太低(低於50%),則說明存在碎片。

 

 

圖解B+數的刪除:

 

    例1

    刪除下圖3階B+樹的關鍵字91



 

首先找到91所在葉節點(最後一個節點),刪除之,以下圖



 

沒有破壞B+樹的性質,刪除完畢

 

    例2

    刪除下圖3階B+樹的關鍵字97



 

    首先找到97所在葉節點(最後一個節點),刪除之,而後修改該節點的父輩的鍵字爲91(只有刪除樹中最大數時要作此步),以下圖

 

 

    例3

    刪除下圖3階B+樹的關鍵字51



 

首先找到51所在節點(第三個節點),刪除之,以下圖



 

破壞了B+樹的性質,從該節點的兄弟節點(左邊或右邊)借節點44,並修改相應鍵值,判斷沒有破壞B+樹,完畢,以下圖

 

 

    例4

    刪除下圖3階B+樹的關鍵字59



 

首先找到59所在葉節點(第三個節點),刪除之,以下圖



 

破壞B+樹性質,嘗試借節點,無效(由於左兄弟節點被借也會破壞B+樹性質),合併第二第三葉節點並調整鍵值,以下圖



 

完畢。

 

     5

    刪除下圖3階B+樹的關鍵字63



 

首先找到63所在葉節點(第四個節點),刪除之,以下圖



 

合併第四五葉節點並調整鍵值,以下圖



 

發現第二層的第二個節點不知足B+樹性質,從第二層的第一個節點借59,並調整鍵值,以下圖



 

 

2.3 B+樹索引對於更新的管理

而對於值被更新對於索引條目的影響,則能夠認爲是刪除和插入的組合。也就是將被更新的舊值對應的索引

 

條目設置爲D(刪除)標記,同時將更新後的值按照順序插入合適的索引塊中。這裏就不重複討論了。

 

3. 重建索引

 

3.1 如何重建索引

 

    ALTER INDEX … REBUILD

 

3.2 重建索引的好處

 

當咱們重建索引之後,在物理上所能得到的好處就是可以減小索引所佔的空間大小(特別是可以減小葉子節

 

點的數量)。而索引大小減少之後,又能帶來如下若干好處:

 

1)CBO對於索引的使用可能會產生一個較小的成本值,從而在執行計劃中選擇使用索引。

 

2)使用索引掃描的查詢掃描的物理索引塊會減小,從而提升效率。

 

3)因爲須要緩存的索引塊減小了,從而讓出了內存以供其餘組件使用。

 

儘管重建索引具備必定的好處,可是盲目的認爲重建索引可以解決不少問題也是不正確的。好比我見過一個

 

生產系統,每隔一個月就要重建全部的索引(並且我相信,不少生產系統可能都會這麼作),其中包括一些

 

100GB的大表。爲了完成重建全部的索引,每每須要把這些工做分散到多個晚上進行。事實上,這是一個

 

7×24的系統,僅重建索引一項任務就消耗了很是多的系統資源。可是每隔一段時間就重建索引有意義嗎?這

 

裏就有一些關於重建索引的很流行的說法,主要包括:

 

1)若是索引的層級超過X(X一般是3)級之後須要經過重建索引來下降其級別。

 

2)若是常常刪除索引鍵值,則須要定時重建索引來收回這些被刪除的空間。

 

3)若是索引的clustering_factor很高,則須要重建索引來下降該值。

 

4)按期重建索引可以提升性能。

 

對於第一點來講,咱們在前面已經知道,B樹索引是一棵在高度上平衡的樹,因此重建索引基本不可能下降其

 

級別,除非是極特殊的狀況致使該索引有很是大量的碎片,致使B樹索引「虛高」,那麼這實際又來到第二點

 

上(由於碎片一般都是因爲刪除引發的)。實際上,對於第一和第二點,咱們應該經過運行ALTER INDEX …

 

REBUILD命令之後檢查indest_stats.pct_used字段來判斷是否有必要重建索引。

 

clustering_factor是經過oracle的索引獲得表數據塊的一個因子,實際上表示index列的排列順序和表中
 
index這個列的排列順序的關係。索引的重建並不能減小clustering_factor,由於重建index不能改變表中數
據存放的順序。一樣,刪除後再建立index也不能下降clustering_factor。下降clustering_factor的關鍵在
於重建表裏的數據。只有將表裏的數據按照索引列排序之後,才能切實有效的下降clustering_factor。可是
若是某個表存在多個索引的時候,須要仔細決定應該選擇哪個索引列來重建表(由於這樣的索引一個表只
有一個)。
 
須要注意的是,clustering_factor主要影響index range scan。好比當一個表有1000條數據,200個數據塊
時,當經過索引去讀取這個表時,須要讀取1000個數據塊。你也許以爲奇怪這個表一共才200個數據塊,怎
麼會須要讀1000個塊呢? 這是由於因爲數據的存放順序是按object_name來存放的,經過index經過
object_id的順序來讀取表必然致使oracle屢次重複讀取一個塊。
 
固然,在oracle 920 開始,對於cluster_factor 比較接近表塊數量的根據索引的大範圍查詢作了特別的處理,
再也不是讀一個索引記錄去搜索一個表記錄了,而是成批處理(經過索引塊一批rowid一次去表塊中得到一批記
錄),這樣就大大節約了讀的成本。
 
關於第四點,建議是不要按期重建索引,能夠是按期檢查索引,只在須要時重建索引。
 
3.3 重建索引對性能的影響
 
最後咱們來看一下重建索引對於性能的提升到底會有什麼做用。假設咱們有一個表,該表具備1百萬條記錄,
佔用了100000個數據塊。而在該表上存在一個索引,在重建以前的pct_used爲50%,高度爲3,分支節點塊數
爲40個,再加一個根節點塊,葉子節點數爲10000個;重建該索引之後,pct_used爲90%,高度爲3,分支節點
塊數降低到20個,再加一個根節點塊,而葉子節點數降低到5000個。那麼從理論上說:
 
1)若是經過索引獲取單獨1條記錄來講:
 
重建以前的成本:1個根+1個分支+1個葉子+1個表塊=4個邏輯讀
 
重建以後的成本:1個根+1個分支+1個葉子+1個表塊=4個邏輯讀
 
性能提升百分比:0
 
2)若是經過索引獲取100條記錄(佔總記錄數的0.01%)來講,分兩種狀況:
 
最差的clustering_factor(即該值等於表的數據行數):
 
重建以前的成本:1個根+1個分支+0.0001*10000(1個葉子)+100個表塊=103個邏輯讀
 
重建以後的成本:1個根+1個分支+0.0001*5000(1個葉子)+100個表塊=102.5個邏輯讀
 
性能提升百分比:0.5%(也就是減小了0.5個邏輯讀)
 
最好clustering_factor(即該值等於表的數據塊):
 
重建以前的成本:1個根+1個分支+0.0001*10000(1個葉子)+0.0001*100000(10個表塊)=13個邏輯讀
 
重建以後的成本:1個根+1個分支+0.0001*5000(1個葉子)+0.0001*100000(10個表塊)=12.5個邏輯讀
 
性能提升百分比:3.8%(也就是減小了0.5個邏輯讀)
 
3)若是經過索引獲取10000條記錄(佔總記錄數的1%)來講,分兩種狀況:
 
最差的clustering_factor(即該值等於表的數據行數):
 
重建以前的成本:1
相關文章
相關標籤/搜索