不平衡的索引?Unbalanced Indexes

xz43發表於2010-12-21

本文翻譯自早年寫在dbazine上的文章 本文的word版本可以到

原文參見:

不平衡的索引?
by Jonathan Lewis

網路上有多篇介紹Oracle索引實現機制的文章,都提及需要經常重建索引。在這些文章中的某處,總是會出現這樣一段簡短的描述, 索引會如何變的不平衡,以及可能導致的後果。很不幸,它們好像忽視了這樣一個事實,Oracle使用的B-tree機制是一種”平衡B-tree”索引,也就是說,索引無法變得不平衡。

“平衡”到底意味著什麼?

既然Oracle的索引使用的是平衡B-tree,為什麼還有如此多的人相信他們的索引會變得不平衡呢?
另外,平衡B-tree到底又是什麼呢?
第二個問題的答案可能能夠幫助我們得到第一個問題的答案。
從技術角度講,平衡B-tree的顯要特性是,在任意時間點,任何葉子節點(leaf block)到根節點(root block)的距離都相等。平衡是指從頂部到底部的平衡。
就Oracle來說,執行一個treedump命令就可以很容易發現這一點,如圖-1所示:

select	object_id
from	user_objects
where	object_type = 'INDEX'
and	object_name = 'T1_IDX1'
-- and 	subobject_name = . . .
;
alter session set events '
'immediate trace name treedump level N';

圖-1: 一次索引樹轉儲涉及到的步驟

首先,需要找到你想要轉儲(dump)索引或者索引分割槽(index partition)的object_id;接著,將object_id作為level的引數來呼叫treedump事件。 如果檢查這個索引樹(tree dump)轉儲生成的跟蹤檔案,你將發現類似於圖-2中所示的結果。

branch: 0x140ff0d 21036813 (0: nrow: 3, level: 2)
   branch: 0x14100ff 21037311 (-1: nrow: 452, level: 1)
      leaf: 0x140ff0e 21036814 (-1: nrow: 359 rrow: 359)
      leaf: 0x14310bb 21172411 (0: nrow: 432 rrow: 432)
      leaf: 0x140ff0f 21036815 (1: nrow: 368 rrow: 368)

圖-2: 從索引樹轉儲結果中提取出的一段內容

這個跟蹤檔案以遞迴降序的方式展示索引的分支塊(branch block 根塊只是分支塊的一個特例)與葉子塊。注意,轉儲內容的第一個塊(根塊)記錄了一個高度(level),並且它下面的每一個分支塊也都記錄了一個高度,但是葉子塊沒有記錄高度。
根塊的高度就是索引的blevel(執行analyze index命令之後會記錄在檢視dba_indexes中)。索引的高度(height ,執行命令validate index後會記錄在檢視index_stats中)就是blevel + 1。
每一個葉子塊到根塊的距離就正好有這麼多步,索引總是平衡的。
那麼為什麼有那麼多人相信Oracle會允許索引變得不平衡呢?

我應負的責任

此時,我必須承認我有罪,僅僅在一年前(2002年5月),我也重複了一個關於塊分裂(index block split)的眾所周知但是卻完全錯誤的描述。雖然我知悉(在我的書(2000年12月,Practical Oracle 8i)中對此做了說明) treedump的細節,但我還是這樣做了。
我猜想,這種錯誤的觀念最初產生於Oracle5(很多年以前)手冊的說明,其大意是,因為”沒有一個葉子塊到根塊的距離比任何其他葉子塊到根塊的距離遠。”, 所以Oracle索引是平衡的。將這個表述與一個過於簡化的塊分裂的圖結合到一起,瞧,一個幾乎牢不可破的神話就誕生了。
圖-3和圖-4描繪了一個非常常見,但是完全錯誤的關於葉子塊如何分裂的概念。

不平衡的索引?Unbalanced Indexes
圖-3: 葉子塊將要分裂前的索引結構

有這樣一個流傳廣泛的說法,葉子塊分裂到兩個全新的塊,這兩個塊分享它的所有資料;接著,Oracle在原來的葉子塊的位置插入一個新的分支塊來持有指向這兩個塊的指標。因此,在這樣一個錯誤的視角下,這個索引的右邊就會比左邊更深。(經常會有人說,建立在基於序列的欄位上的索引會帶來最大的問題,因為,從這個理論來推導,最右邊的葉子塊到根塊的距離會越來越遠,每分裂一次,就會降低一層)
不平衡的索引?Unbalanced Indexes
圖-4 這不是葉子塊分裂發生的方式

事實上,這個工作Oracle做得更加精緻前瞻並有效得多。 圖-5所示是一個複雜的葉子塊分裂的結果。
不平衡的索引?Unbalanced Indexes
圖-5: 一次遞迴分裂後的索引結構圖

由於這個葉子塊分裂成了兩個塊,Oracle會嘗試往當前指向這個葉子塊的分支塊中插入一個額外的指標。
但是,如果這個分支塊也滿了,Oracle會繼續將這個分支塊也分裂成兩個塊,並且在兩個分支塊之間分配現存塊裡的指向葉子塊的指標,並且(遞迴地) 在這個分支塊的上一級的分支塊中插入一個指向這個新的分支塊的新的分支指標。
如果在這個過程中,Oracle抵達了根塊,然而這個根塊也滿了,那麼根塊也必須分裂。在這種情況下,Oracle將建立一個新的根塊來持有這兩個分支指標。(事實上,Oracle處理根塊分裂的方式與處理普通的分支塊的分裂有一點細微的差異,以確保無論根塊上發生了多少次的分裂,總是可以在物理段的同一個位置找到根塊。)
注意,這個遞迴的分裂操作這樣沿著索引樹不斷攀升意味著, 無論何時,索引總能保持平衡。

為什麼這個神話如此牢固?

為什麼這個關於不平衡的索引的神話能夠長盛不衰,是否存在一些原因呢?這答案是確實存在一些原因。

要切記,當我們討論B-tree的時候,單詞“balanced ”的定義有非常嚴格的含義,然而,這個單詞還有一種完全不同的解釋。

例如,你將如何描述圖-6中的這個索引,其中根塊指向六個葉子塊,但是其中一個葉子塊是空的,有三個塊幾乎是空的,還有兩個塊塞的很滿。(注意,從根塊到葉子塊的這些額外的說明是為了強調索引填充分佈的多麼不均勻;實際上,根塊到每個葉子塊都只有一個指標)
不平衡的索引?Unbalanced Indexes
圖-6 “不平衡”的一種非技術理解

 看到這種圖案的索引,一個”人”的真實反應都會認為它”不平衡”。很明顯,索引的右手邊要比左手邊”重”。很不幸,當技術表達意味著完全不同的東西的時候,這種非正式的人為表達應該更加恰當。

或許正是這種技術表達與非正式的人為表達之間的衝突導致了這種混淆。

在這種非正式的意義上,在基於序列值的欄位上索引很容易就會變得”不平衡”,特別是它們被用來表徵/處理先進先出(FIFO)佇列機制的時候。然而,即使它們(在非正式語境中)是不平衡的,它們(在技術上)仍然是平衡的B-tree。

(推動使用類似於”扭曲”或者”分佈不均勻”作為術語來描述這種型別的索引,或許會是個好主意。)

有時,僅僅幾篇草率地使用術語的文章或報告就可能構建一個神話,在這個例子中,就是一個導致眾多DBA浪費無數小時的時間去做不必要的索引重建的神話。

記住:你下一次認為Oracle表現愚蠢或者低效的時候,很可能問題是出在一個古老的誤解上面,而不是Oracle軟體本身的問題。

告警提醒

如果你還想進一步地研究,treedump選項還有一系列的問題需要注意。對於大部分的Oracle版本,它看似對索引段中的每個塊都生成一行輸出,這樣可能會非常昂貴並且速度緩慢,因為它需要按順序訪問索引中的每一個資料塊。然而,在Oracle 9.0中,跟蹤檔案看似會對每一個塊做一個整塊轉儲,這樣會使得轉儲檔案非常巨大,轉儲速度也會非常緩慢。

第二個問題是所有版本都一致的。如果這個索引是在定義主鍵約束或者唯一鍵約束時生成的,Oracle就會設定ind$表的flags欄位的第13位,而這將導致treedump程式崩潰並報出錯誤”invalid value”。分割槽索引的分割槽段不會產生這個問題,但是對於所有其他型別的主鍵索引與唯一索引(包含非分割槽索引組織表,IOT),這都很惱人。先建立索引,再基於這個索引建立約束通常是個好主意, 這樣處理可以避免除索引組織表外的其他所有條件下的問題。在緊急情況下,你可以修改ind$表來清除這一位,但是很明顯,需要先取得Oracle支援的認可。

結論

當談到平衡B-tree索引的時候,術語平衡指的是從頂部到底部,而不是從左到右。

 

Oracle確實實現了一個版本的平衡B-tree索引,因此在任何時候,索引中的所有葉子塊到根塊的距離都是完全相同的。如果最近對這個索引作過分析的話,可以從檢視user_indexes的欄位blevel找到它。如果剛剛對這個索引執行過validate index的話,可以從檢視index_statsheight欄位(等價於blevel+1)得到。

 

當聽到你應該經常重建索引,因為這些索引已經變得不平衡時,要抵制這種理由,因為它不是一個靠得住的理由。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/9399028/viewspace-682233/,如需轉載,請註明出處,否則將追究法律責任。

相關文章