oracle B*樹索引原理學習

    B*Tree索引是最常見的索引結構,默認建立的索引就是這種類型的索引。B*Tree索引在檢索高基數數據列(高基數數據列是指該列有很多不同的值)時提供了最好的性能。當取出的行數佔總行數比例較小時B-Tree索引比全表檢索提供了更有效的方法。但當檢查的範圍超過表的10%時就不能提高取回數據的性能。B-Tree索引是基於二叉樹的,由分支塊(branch block)和葉塊(leaf block)組成。在樹結構中,位於最底層底塊被稱爲葉塊,包含每個被索引列的值和行所對應的rowid。在葉節點的上面是分支塊,用來導航結構,包含了索引列(關鍵字)範圍和另一索引塊的地址。

   理想情況下,樹的高度應該最小,因爲爲了獲取某個信息而進行的讀操作的次數與樹的高度成正比。爲了定位某個關鍵字,最少需要進行三個讀操作(兩個讀操作發生在根/分支級,第三個讀動作發生在葉節點級)。需要記住的是,根據索引所佈局的方式不同,每個讀操作都有可能是邏輯讀或物理讀。如果單獨的某個讀操作將所需要的塊放置在內存中,那麼後面的讀操作將有可能直接從內存中進行;否則,它們將需要從硬盤中進行讀。高度與階數成反比,階數越高,高度將越低(這種樹稱爲“扁平”樹)。在任何B*樹的實現中,一個重要目標就是增加分支數、減少高度,從而保證對葉節點的訪問被加速。
    在B *樹的實現過程中,主要應該注意的問題是INSERT操作、DELETE操作與UPDATE操作。


   在每個INSERT操作過程中,關鍵字必須被插入在正確葉節點的位置。如果葉節點已滿,不能容納更多的關鍵字,就必須將葉節點拆分。拆分的方法有兩種:
1)如果新關鍵字值在所有舊葉節點塊的所有關鍵字中是最大的,那麼所有的關鍵字將按照99:1的比例進行拆分,使得在新的葉節點塊中只存放有新關鍵字,而其他的所有關鍵字(包括所有刪除的關鍵字)仍然保存在舊葉節點塊中。
2)如果新關鍵字值不是最大的,那麼所有的關鍵字將按照50:50的比例進行拆分,這時每個葉節點塊(舊與新)中將各包含原始葉節點中的一半關鍵字。
    這個拆分必須通過一個指向新葉節點的新入口向上傳送到父節點。如果父節點已滿,那麼這個父節點也必須進行拆分,並且需要將這種拆分向上傳送到父節點的父節點。這時,如果這個父節點也已滿,將繼續進行這個過程。這樣,某個拆分可能最終被一直傳送到根節點。如果根節點滿了,根結點也將進行分裂。根結點在進行分裂的時候,就是樹的高度增加的時候。根節點進行分裂的方式跟其他的的節點分裂的方式相比較,在物理位置上的處理也是不同的。根節點分裂時,將原來的根結點分裂爲分支節點或葉節點,保存到新的塊中,而將新的根節點信息保存到原來的根結點塊中,這樣做的是爲因爲避免修改數據字典所帶來的相對較大的開銷。
    在索引的每一個層次之間,每一個層最左邊的節點的block頭部都有一個 指向下層最左邊的塊的指針,這樣有利於 fast full scan 的快速定位最左邊的葉子節點。

   每個拆分過程都是要花費一定的開銷的,特別是要進行物理硬盤I/O動作。此外,在進行拆分之前,Oracle必須查找到一個空塊,用來保存這個拆分。可以用以下步驟來進行查找空塊的動作:
1) 在索引的自由列表(free-list, 又稱爲空閒列表) 中查到一個空閒塊,可以通過CREATE/ALTER INDEX命令爲一個索引定義多個空閒列表。索引空閒列表並不能幫助Oracle查找一個可用來存放將要被插入的新關鍵字的塊。這是因爲關鍵字值不能隨機地存放在索引中可用的第一個“空閒”葉節點塊中,這個值必須經過適當的排序之後,放置在某個特定的葉節點塊中。只有在塊拆分過程中才需要使用索引的空閒列表,每個空閒列表都包含有一個關於“空”塊的鏈接列表。當爲某個索引定義了多個空閒列表時,首先將從分配給進程的空間列表中掃描一個空閒塊。如果沒有找到所需要的空閒塊,將從主空閒列表中進行掃描空閒塊的動作。
2) 如果沒有找到任何空閒塊,Oracle將試圖分配另一個擴展段。如果在表空間中沒有更多的自由空間,Oracle將產生錯誤ORA-01654。
3) 如果通過上述步驟,找到了所需的空閒塊,那麼這個索引的高水位標(HWM)將加大。
4) 所找到的空閒塊將用來執行拆分動作。
    在創建B*樹索引時,一個需要注意的問題就是要避免在運行時進行拆分,或者,要在索引創建過程中進行拆分(“預拆分”),從而使得在進行拆分時能夠快速命中,以便避免運行時插入動作。當然,這些拆分也不僅僅侷限於插入動作,在進行更新的過程中也有可能會發生拆分動作。

下面來分析一下在Oracle中進行已索引關鍵字的UPDATE動作時,會發生什麼事情。
    索引更新完全不同於表更新,在表更新中,數據是在數據塊內部改變的(假設數據塊中有足夠的空間來允許進行這種改變);但在索引更新中,如果有關鍵字發生改變,那麼它在樹中的位置也需要發生改變。請記住,一個關鍵字在B *樹中有且只有一個位置。因此,當某個關鍵字
發生改變時,關鍵字的舊錶項必須被刪除,並且需要在一個新的葉節點上創建一個新的關鍵字。舊的表項有可能永遠不會被重新使用,這是因爲只有在非常特殊的情況下, Oracle纔會重用關鍵字表項槽,例如,新插入的關鍵字正好是被刪除的那個關鍵字(包括數據類型、長度
等等)。(這裏重用的是塊,但完全插入相同的值的時候,也不一定插入在原來的被刪除的位置,只是插入在原來的塊中,可能是該塊中的一個新位置。也正因爲如此,在索引塊中保存的的記錄可能並不是根據關鍵字順序排列的,隨着update等的操作,會發生變化。)那麼,這種情況發生的可能性有多大呢?許多應用程序使用一個數列來產生NUMBER關鍵字(特別是主關鍵字)。除非它們使用了RECYCLE選項,否則這個數列將不會兩次產生完全相同的數。這樣,索引中被刪除的空間一直沒有被使用。這就是在大規模刪除與更新過程中,表大小不斷減小或至少保持不變但索引不斷加大的原因。

通過上面對B *樹的分析,可以得出以下的應用準則:
1)避免對那些可能會產生很高的更新動作的列進行索引。
2)避免對那些經常會被刪除的表中的多個列進行索引。若有可能,只對那些在這樣的表上會進行刪除的主關鍵字與/或列進行索引。如果對多個列進行索引是不可避免的,那麼就應該考慮根據這些列對錶進行劃分,然後在每個這樣的劃分上執行TRUNCATE動作(而不是DELETE動作)。TRUNCATE在與DROP STORAGE短語一同使用時,通過重新設置高水位標來模擬刪除表與索引以及重新創建表與索引的過程。
3)避免爲那些唯一度不高的列創建B*樹索引。這樣的低選擇性將會導致樹節點塊的稠密性,從而導致由於索引“平鋪( flat)”而出現的大規模索引掃描。唯一性的程度越高,性能就越好,因爲這樣能夠減少範圍掃描,甚至可能用唯一掃描來取代範圍掃描。
4)空值不應該存儲在單列索引中。對於複合索引的方式,只有當某個列不空時,才需要進行值的存儲。在爲DML語句創建IS NULL或IS NOT NULL短語時,應該切記這個問題。
5)IS NULL不會導致索引掃描,而一個沒有帶任何限制的IS NOT NULL則可能會導致完全索引掃描。


2. PCTFREE的重要性
    對於B*樹索引, PCTFREE可以決定葉節點拆分的extent。也就是說,PCTFREE用來說明在某個塊中的自由空間數目,以便於後來的更新動作。但是,對於索引(與表不同),這些更新動作沒有任何意義,因爲更新會刪除一個索引,然後又出現插入一個新索引。
    對於索引,PCTFREE大多數是在索引創建過程中發生作用,可以用一個非零值來說明塊拆分比例。如果在索引創建過程中,PCTFREE被設置爲20,那麼有80%的葉節點將可能會包含關鍵字信息。但是,剩餘的20%將用來作爲關鍵字信息後來插入到葉節點塊中時使用。這樣將能夠保證在需要進行葉節點塊的拆分時,運行時的插入開銷最小。雖然一個較高的PCTFREE可能會使得索引創建時間增加,但它能夠防止在實際的使用過程中命中性能的降低。因此,那些正在等待某個行被插入的終端用戶並不會因爲需要進行葉節點塊的拆分而使得自己的性能受到影響。
基於上述信息,可以得出以下結論:
1)某個索引的PCTFREE主要是在索引創建時使用。在實際的應用過程中,PCTFREE將被忽略。
2)如果表是一個經常被訪問、包含有大量DML改變(通過交互式用戶屏幕)的表,那麼就應該爲OLTP應用程序指定一個較高的PCTFREE。
3)如果索引創建時間很關鍵,那麼就應該指定一個較低PCTFREE。這樣在每個葉節點塊中將會壓縮有多個行,從而可以避免在索引創建時進行更多的拆分。這一點對於2 4×7客戶站點非常重要,因爲在大多數情況下索引創建過程需要很多的系統停工時間(特別是在表有幾百萬行時更爲如此)。
4)對於任何其值不斷增加的列,最好是設置一個非常低的PCTFREE(甚至可以爲0)。這是因爲只有那些最右方的葉節點塊總是會被插入,從而使得樹向右增長。而左邊的葉節點將一直爲靜止狀態,因此沒有必要使得這些塊的任何部分爲空(通過使用非零PCTFREE)。



索引的實驗:

SQL> create table test(id number,name varchar2(100));
表已創建
SQL> create index idx_test on test(id);
索引已創建
SQL>  select file_id,extent_id,block_id from dba_extents where segment_name='IDX_TEST' ;

   FILE_ID  EXTENT_ID   BLOCK_ID
---------- ---------- ----------
         6          0         41

此時索引爲空,我們來轉儲一下41號塊的信息

SQL>  alter system dump datafile 6 block 41;
系統已更改。

可以通過以下的方法得到轉儲的數據文件

在Oracle Database 11g之前,要想獲得跟蹤文件的名稱,通常我們需要執行一系列的查詢,常用的腳本如下:

SQL> SELECT a.VALUE || b.symbol || c.instance_name || '_ora_' || d.spid || '.trc'
  2            trace_file
  3    FROM (SELECT VALUE
  4            FROM v$parameter
  5           WHERE NAME = 'user_dump_dest') a,
  6         (SELECT SUBSTR (VALUE, -6, 1) symbol
  7            FROM v$parameter
  8           WHERE NAME = 'user_dump_dest') b,
  9         (SELECT instance_name FROM v$instance) c,
 10         (SELECT spid
 11            FROM v$session s, v$process p, v$mystat m
 12           WHERE s.paddr = p.addr AND s.SID = m.SID AND m.statistic# = 0) d
 13  /

TRACE_FILE
--------------------------------------------------------------------------------

F:\ORACLE\PRODUCT\10.2.0\ADMIN\ORCL\UDUMP\orcl_ora_10076.trc

11g使用如下腳本,得到轉儲文件

SQL> SELECT VALUE FROM V$DIAG_INFO WHERE NAME = "Default Trace File";

VALUE

-------------------------------------------------------------------------------

F:\ORACLE\PRODUCT\10.2.0\ADMIN\ORCL\UDUMP\orcl_ora_10076.trc


既然已經找到了轉儲文件,那就看看轉儲文件的內容:


Start dump data blocks tsn: 7 file#: 6 minblk 41 maxblk 41  --轉儲的文件號和塊號

buffer tsn: 7 rdba: 0x01800029 (6/41)

scn: 0x0000.000bddf9 seq: 0x01 flg: 0x04 tail: 0xddf92001

frmt: 0x02 chkval: 0x4b0e type: 0x20=FIRST LEVEL BITMAP BLOCK --這個表示它是一級位圖塊,直接和數據塊打交道

Hex dump of block: st=0, typ_found=1




Dump of First Level Bitmap Block

 --------------------------------

   nbits : 2 nranges: 1         parent dba:  0x0180002a   poffset: 0      --標紅的表示它的父級位圖塊

   unformatted: 12      total: 16        first useful block: 3      

   owning instance : 1

   instance ownership changed at 04/20/2013 12:08:33

   Last successful Search 04/20/2013 12:08:33

   Freeness Status:  nf1 0      nf2 1      nf3 0      nf4 0      

 

   Extent Map Block Offset: 4294967295 

   First free datablock : 3      

   Bitmap block lock opcode 0

   Locker xid:     :  0x0000.000.00000000

   Inc #: 0 Objd: 52590 

  HWM Flag: HWM Set

      Highwater::  0x0180002d  ext#: 0      blk#: 4      ext size: 16    

  #blocks in seg. hdr's freelists: 0     

  #blocks below: 1     

  mapblk  0x00000000  offset: 0     

  --------------------------------------------------------

  DBA Ranges :

  --------------------------------------------------------

   0x01800029  Length: 16     Offset: 0      

   0:Metadata   1:Metadata   2:Metadata   3:25-50% free 

   4:unformatted   5:unformatted   6:unformatted   7:unformatted 

   8:unformatted   9:unformatted   10:unformatted   11:unformatted

   12:unformatted   13:unformatted   14:unformatted   15:unformatted

  --------------------------------------------------------

End dump data blocks tsn: 7 file#: 6 minblk 41 maxblk 41


-標紅的這些塊被用來存儲元數據,其餘的塊用來存儲數據,注意到總共有16個塊,full表示數據塊已經用完了。

如果這些塊用完的話,oracle就會使用二級位圖結構,以此類推,會出現三級位圖結構,oracle目前支持到三級位圖結構,上面的表示這些塊暫時未被使用,根據以上的說明oracle會從第4個塊(44)開始存放索引,索引的branch_code應該放在第44塊中,接下來我們就分析索引是如何分佈的。

下面是一個rdba轉換爲具體的文件和塊號的方法,後面會用到。


create or replace function getbfno(p_dba in varchar2)

  return varchar2

 is

 l_str varchar2(255) default null; 

 begin 

   l_str :='datafile# is:'

   ||dbms_utility.data_block_address_file(TO_NUMBER(LTRIM(P_DBA,'0x'),'xxxxxxxx'))

   ||chr(10) ||'datablock is:'

   ||dbms_utility.data_block_address_block(TO_NUMBER(LTRIM(P_DBA,'0x'),'xxxxxxxx'));

    return l_str;

   end;


--去上面的rdba得到如下的轉換

SQL> select getbfno('0x01800029') from dual;


GETBFNO('0X01800029')

---------------------------------------------

datafile# is:6

datablock is:41


在開始插入索引前,我們下去看看  parent dba:  0x0180002a 這個裏面有什麼東西,也就是是二級位圖塊。

SQL> select getbfno('0x0180002a') from dual;

GETBFNO('0X0180002A')

-------------------------------------------------

datafile# is:6

datablock is:42


我們看到二級位圖塊是第42塊,轉儲42塊得到以下內容

*** 2013-04-20 15:30:52.140

*** SERVICE NAME:(SYS$USERS) 2013-04-20 15:30:52.125

*** SESSION ID:(121.797) 2013-04-20 15:30:52.125

Start dump data blocks tsn: 7 file#: 6 minblk 42 maxblk 42

buffer tsn: 7 rdba: 0x0180002a (6/42)

scn: 0x0000.000bddf2 seq: 0x01 flg: 0x04 tail: 0xddf22101

frmt: 0x02 chkval: 0x4be9 type: 0x21=SECOND LEVEL BITMAP BLOCK --表示它是二級位圖

Hex dump of block: st=0, typ_found=1

Dump of memory from 0x09142200 to 0x09144200

9142200 0000A221 0180002A 000BDDF2 04010000  [!...*...........]

9142210 00004BE9 00000000 00000000 00000000  [.K..............]

9142220 00000000 00000000 00000000 00000000  [................]

        Repeat 1 times

9142240 00000000 00000000 00000000 0180002B  [............+...]

9142250 00000001 00000001 00000000 00000000  [................]

9142260 00000000 00000000 0000CD6E 00000001  [........n.......]

9142270 00000000 01800029 00010005 00000000  [....)...........]

9142280 00000000 00000000 00000000 00000000  [................]

        Repeat 502 times

91441F0 00000000 00000000 00000000 DDF22101  [.............!..]

Dump of Second Level Bitmap Block

   number: 1       nfree: 1       ffree: 0      pdba:     0x0180002b  --此處它指向了43塊

   Inc #: 0 Objd: 52590

  opcode:0 

 xid: 

  L1 Ranges :

  --------------------------------------------------------

   0x01800029  Free: 5 Inst: 1       --在此處它指向了25塊,


   這一塊用來記錄那些位圖塊管理單元被包含在對象中

  --------------------------------------------------------

End dump data blocks tsn: 7 file#: 6 minblk 42 maxblk 42


在上面看到一塊,其中pdba指向了43塊,不明白是做什麼的,順便看一下

Dump of Second Level Bitmap Block

   number: 1       nfree: 1       ffree: 0      pdba:     0x0180002b  --此處它指向了43塊

   Inc #: 0 Objd: 52590

  opcode:0 

43塊的內容經過轉儲內容如下:(中間省略了一部分省略了)

*** 2013-04-20 15:40:25.406

Start dump data blocks tsn: 7 file#: 6 minblk 43 maxblk 43

buffer tsn: 7 rdba: 0x0180002b (6/43)

scn: 0x0000.000bddf9 seq: 0x01 flg: 0x04 tail: 0xddf92301

frmt: 0x02 chkval: 0x66e9 type: 0x23=PAGETABLE SEGMENT HEADER

Hex dump of block: st=0, typ_found=1

  Extent Control Header

  -----------------------------------------------------------------

  Extent Header:: spare1: 0      spare2: 0      #extents: 1      #blocks: 16    

                  last map  0x00000000  #maps: 0      offset: 2716  

      Highwater::  0x0180002d  ext#: 0      blk#: 4      ext size: 16    

  #blocks in seg. hdr's freelists: 0     

  #blocks below: 1     

  mapblk  0x00000000  offset: 0     

                   Unlocked

  --------------------------------------------------------

  Low HighWater Mark : 

      Highwater::  0x0180002d  ext#: 0      blk#: 4      ext size: 16    

  #blocks in seg. hdr's freelists: 0     

  #blocks below: 1     

  mapblk  0x00000000  offset: 0     

  Level 1 BMB for High HWM block: 0x01800029   --此處標註了高低水位線

  Level 1 BMB for Low HWM block: 0x01800029

  --------------------------------------------------------

  Segment Type: 2 nl2: 1      blksz: 8192   fbsz: 0      

  L2 Array start offset:  0x00001434

  First Level 3 BMB:  0x00000000

  L2 Hint for inserts:  0x0180002a

  Last Level 1 BMB:  0x01800029

  Last Level II BMB:  0x0180002a

  Last Level III BMB:  0x00000000

     Map Header:: next  0x00000000  #extents: 1    obj#: 52590  flag: 0x10000000

  Inc # 0 

  Extent Map

  -----------------------------------------------------------------

   0x01800029  length: 16     

  

  Auxillary Map

  --------------------------------------------------------

   Extent 0     :  L1 dba:  0x01800029 Data dba:  0x0180002c 


--如果已經有空間被使用,則會指出該空間的第一個數據塊的位置,可以通過此處找到branch_code

  --------------------------------------------------------

  

   Second Level Bitmap block DBAs  --此處記錄了二級位圖的地址

   --------------------------------------------------------

   DBA 1:   0x0180002a

  

End dump data blocks tsn: 7 file#: 6 minblk 43 maxblk 43


這一塊應該叫做三級位圖,如果PAGETABLE SEGMENT HEADER的空間不足以存儲二級位圖塊的指針,那麼新的三級位圖被創建。

關於oracle的段空間的分配大概有個瞭解以後,我們開始做索引的物理存放:

運行以下過程,在表中插入數據後,我們來看看索引存儲的變化

SQL> declare
  2    begin
  3      for i in 1..10000
  4
  5         loop
  6
  7         insert into test values (i,'100');
  8         commit;
  9         end loop ;
 10
 11      end;
 12
 13       /

PL/SQL 過程已成功完成。


--在查看發現索引使用了兩個區間
SQL> select file_id,extent_id,block_id from dba_extents where segment_name='IDX_
TEST' ;

   FILE_ID  EXTENT_ID   BLOCK_ID
---------- ---------- ----------
         6          0         41
         6          1         57

SQL>  analyze index idx_test validate structure;

索引已分析

SQL>  select btree_space,used_space,pct_used,blocks,lf_blks,br_blks,height from index_stats;

BTREE_SPACE USED_SPACE   PCT_USED     BLOCKS    LF_BLKS    BR_BLKS     HEIGHT
----------- ---------- ---------- ---------- ---------- ---------- ----------
     160032     149999         94         32         19          1          2

--轉儲41看到了數據塊全部被使用了
  --------------------------------------------------------
  DBA Ranges :
  --------------------------------------------------------
   0x01800029  Length: 16     Offset: 0      
  
   0:Metadata   1:Metadata   2:Metadata   3:FULL
   4:FULL   5:FULL   6:FULL   7:FULL
   8:FULL   9:FULL   10:FULL   11:FULL
   12:FULL   13:FULL   14:FULL   15:FULL
  --------------------------------------------------------
End dump data blocks tsn: 7 file#: 6 minblk 41 maxblk 41

--轉儲57的文件看到以下的內容,自己分析一下吧

Dump of First Level Bitmap Block
 --------------------------------
   nbits : 2 nranges: 1         parent dba:  0x0180002a   poffset: 1     
   unformatted: 0       total: 16        first useful block: 1      
   owning instance : 1
   instance ownership changed at 04/20/2013 16:00:48
   Last successful Search 04/20/2013 16:00:48
   Freeness Status:  nf1 0      nf2 9      nf3 0      nf4 0      
 
   Extent Map Block Offset: 4294967295 
   First free datablock : 3      
   Bitmap block lock opcode 0
   Locker xid:     :  0x0000.000.00000000
   Inc #: 0 Objd: 52590 
  HWM Flag: HWM Set
      Highwater::  0x01800049  ext#: 1      blk#: 16     ext size: 16    
  #blocks in seg. hdr's freelists: 0     
  #blocks below: 28    
  mapblk  0x00000000  offset: 1     
  --------------------------------------------------------
  DBA Ranges :
  --------------------------------------------------------
   0x01800039  Length: 16     Offset: 0      
  
   0:Metadata   1:FULL   2:FULL   3:25-50% free
   4:25-50% free   5:25-50% free   6:25-50% free   7:25-50% free
   8:25-50% free   9:25-50% free   10:25-50% free   11:25-50% free
   12:FULL   13:FULL   14:FULL   15:FULL
  --------------------------------------------------------
End dump data blocks tsn: 7 file#: 6 minblk 57 maxblk 57

現在我們要轉儲43塊,去看一下每一個區的第一個數據塊的編號

  Auxillary Map
  --------------------------------------------------------
   Extent 0     :  L1 dba:  0x01800029 Data dba:  0x0180002c
   Extent 1     :  L1 dba:  0x01800039 Data dba:  0x0180003a
  --------------------------------------------------------


SQL> select getbfno('0x0180002c') from dual;

GETBFNO('0X0180002C')
--------------------------------------------------

datafile# is:6
datablock is:44

44塊中的內容如下:(截取一部分)

Block header dump:  0x0180002c

 Object id on Block? Y

 seg/obj: 0xcd6e  csc: 0x00.c43b8  itc: 1  flg: E  typ: 2 - INDEX

     brn: 0  bdba: 0x1800029 ver: 0x01 opc: 0

     inc: 0  exflg: 0

 

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc

0x01   0x0004.018.00000189  0x00800639.0165.02  C---    0  scn 0x0000.000c43b7

 

Branch block dump

=================

header address 152314444=0x914224c

kdxcolev 1

KDXCOLEV Flags = - - -

kdxcolok 0

kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y

kdxconco 2

kdxcosdc 1

kdxconro 18

kdxcofbo 64=0x40

kdxcofeo 7898=0x1eda

kdxcoavs 7834

kdxbrlmc 25165871=0x180002f

kdxbrsno 17

kdxbrbksz 8060 

kdxbr2urrc 0

row#0[8051] dba: 25165872=0x1800030 --從這可以看到葉子節點的存儲位置

col 0; len 3; (3):  c2 06 2a

col 1; TERM

row#1[8042] dba: 25165873=0x1800031

col 0; len 3; (3):  c2 0b 4b

col 1; TERM

row#2[8033] dba: 25165874=0x1800032

col 0; len 3; (3):  c2 11 08

col 1; TERM

row#3[8024] dba: 25165875=0x1800033

col 0; len 3; (3):  c2 16 29

col 1; TERM

row#4[8015] dba: 25165876=0x1800034

col 0; len 3; (3):  c2 1b 4a

col 1; TERM

row#5[8006] dba: 25165877=0x1800035

col 0; len 3; (3):  c2 21 07

col 1; TERM

row#6[7997] dba: 25165878=0x1800036

col 0; len 3; (3):  c2 26 28

col 1; TERM


看到這兒要了解一下dump這個函數

dump 函數能查看錶中列在datafile存儲內容。
Oracle的NUMBER類型最多由三個部分構成,這三個部分分別是最高位表示位、數據部分、符號位。其中負數包含符號位,正數不會包括符號位(10進制即102)。另外,數值0比較特殊,它只包含一個數值最高位表示位80(16進制),沒有數據部分。

DUMP函數的輸出格式類似:

類型 <[長度]>,符號/指數位 [數字1,數字2,數字3,......,數字20]

各位的含義如下:

1.類型: Number型,Type=2 (類型代碼可以從Oracle的文檔上查到)

2.長度:指存儲的字節數

3.符號/指數位

在存儲上,Oracle對正數和負數分別進行存儲轉換:

正數:加1存儲(爲了避免Null)
負數:被101減,如果總長度小於21個字節,最後加一個102(是爲了排序的需要)

指數位換算:

正數:指數=符號/指數位 - 193 (最高位爲1是代表正數) 
負數:指數=62 - 第一字節

4.從<數字1>開始是有效的數據位

從<數字1>開始是最高有效位,所存儲的數值計算方法爲:

將下面計算的結果加起來:

每個<數字位>乘以100^(指數-N) (N是有效位數的順序位,第一個有效位的N=0)


舉例說明

SQL> select dump(123456.789) from dual;
DUMP(123456.789)
-------------------------------
Typ=2 Len=6: 195,13,35,57,79,91

<指數>:   195 - 193 = 2 
<數字1>    13 - 1    = 12 *100^(2-0) 120000 
<數字2>    35 - 1    = 34 *100^(2-1) 3400 
<數字3>    57 - 1    = 56 *100^(2-2) 56 
<數字4>    79 - 1    = 78 *100^(2-3) .78 
<數字5>    91 - 1    = 90 *100^(2-4) .009 
                            123456.789


SQL> select dump(-123456.789) from dual;
DUMP(-123456.789)
----------------------------------
Typ=2 Len=7: 60,89,67,45,23,11,102

<指數>     62 - 60 = 2(最高位是0,代表爲負數) 
<數字1> 101 - 89 = 12 *100^(2-0) 120000 
<數字2> 101 - 67 = 34 *100^(2-1) 3400 
<數字3> 101 - 45 = 56 *100^(2-2) 56 
<數字4> 101 - 23 = 78 *100^(2-3) .78 
<數字5> 101 - 11 = 90 *100^(2-4) .009 
                              123456.789(-)

現在我們查看葉子節點


SQL> select getbfno('0x1800030') from dual;


GETBFNO('0X1800030')

-----------------------------------------------

datafile# is:6

datablock is:48

轉儲一下48號數據塊可以看到:

Block header dump:  0x01800030

 Object id on Block? Y

 seg/obj: 0xcd6e  csc: 0x00.c16d6  itc: 2  flg: E  typ: 2 - INDEX

     brn: 0  bdba: 0x1800029 ver: 0x01 opc: 0

     inc: 0  exflg: 0


 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc

0x01   0x0001.026.00000179  0x00800012.00cf.01  -BU-    1  fsc 0x0000.000c16d9

0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000


Leaf block dump

===============

header address 152314468=0x9142264

kdxcolev 0

KDXCOLEV Flags = - - -

kdxcolok 1

kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y

kdxconco 2

kdxcosdc 2

kdxconro 533

kdxcofbo 1102=0x44e

kdxcofeo 1112=0x458

kdxcoavs 10

kdxlespl 0

kdxlende 0

kdxlenxt 25165873=0x1800031

kdxleprv 25165871=0x180002f

kdxledsz 0

kdxlebksz 8036

row#0[1112] flag: ------, lock: 0, len=13

col 0; len 3; (3):  c2 06 2a

col 1; len 6; (6):  01 80 00 1c 02 1c

row#1[1125] flag: ------, lock: 0, len=13

col 0; len 3; (3):  c2 06 2b

col 1; len 6; (6):  01 80 00 1c 02 1d

row#2[1138] flag: ------, lock: 0, len=13

col 0; len 3; (3):  c2 06 2c

col 1; len 6; (6):  01 80 00 1c 02 1e

row#3[1151] flag: ------, lock: 0, len=13

col 0; len 3; (3):  c2 06 2d

col 1; len 6; (6):  01 80 00 1c 02 1f

row#4[1164] flag: ------, lock: 0, len=13

col 0; len 3; (3):  c2 06 2e

col 1; len 6; (6):  01 80 00 1c 02 20後面省略


注意到以下:

row#0[8051] dba: 25165872=0x1800030 --從這可以看到葉子節點的存儲位置

col 0; len 3; (3):  c2 06 2a      --541

col 1; TERM


row#0[1112] flag: ------, lock: 0, len=13

col 0; len 3; (3):  c2 06 2a          --541

col 1; len 6; (6):  01 80 00 1c 02 1c   --rowid

分支節點存放了第一個葉子節點的值,這樣在進行範圍判斷時候直接使用葉子節點中的值來判斷值位於哪一個塊中。

刪除541這一行數據

row#0[1112] flag: ---D--, lock: 2, len=13    --表示數據已經被刪除

col 0; len 3; (3):  c2 06 2a

col 1; len 6; (6):  01 80 00 1c 02 1c

row#0[8051] dba: 25165872=0x1800030

col 0; len 3; (3):  c2 06 2a

col 1; TERM

發現branch_code並沒有刪除,還是指向了它,並且在葉子節點並沒有刪除它,所以當DELETE 和 insert 很頻繁的時候,索引會越來越龐大。

因爲使用DELETE刪除數據並不會刪除索引,只會標記爲索引無效。

下面我們來看這一行

row#1[1125] flag: ------, lock: 0, len=13

col 0; len 3; (3):  c2 06 2b  -- 542

col 1; len 6; (6):  01 80 00 1c 02 1d


我們來更新這一行  ,將他的值變爲545 看看索引會發生什麼變化。

SQL> update test set id='545' where id='542';

已更新 1 行。

row#0[8023] flag: ---D--, lock: 2, len=13
col 0; len 3; (3):  c2 06 2b
col 1; len 6; (6):  01 80 00 1c 02 1d

並且增加了一行

row#3[1112] flag: ------, lock: 2, len=13

col 0; len 3; (3):  c2 06 2e

col 1; len 6; (6):  01 80 00 1c 02 1d

rowid是完全一樣的,同時發現一個問題

前面刪除的哪一行也不在了,541哪一行的索引沒有在出現,難道使用UPADATE 運算會刪除以前被刪除了的索引,這個是個疑問,在去試一下。好像是這樣的,這個問題還有待研究。


Leaf block dump

===============

header address 152314468=0x9142264

kdxcolev 0

KDXCOLEV Flags = - - -

kdxcolok 0

kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y

kdxconco 2

kdxcosdc 2

kdxconro 533

kdxcofbo 1102=0x44e

kdxcofeo 1112=0x458

kdxcoavs 10

kdxlespl 0

kdxlende 1

kdxlenxt 25165873=0x1800031

kdxleprv 25165871=0x180002f

kdxledsz 0

kdxlebksz 8036

row#0[8023] flag: ---D--, lock: 2, len=13  --頭變成這樣了

col 0; len 3; (3):  c2 06 2c   --這一行被我update掉了,但是前面剛剛廢棄的索引不見了。

col 1; len 6; (6):  01 80 00 1c 02 1e

row#1[8010] flag: ------, lock: 0, len=13

col 0; len 3; (3):  c2 06 2d

col 1; len 6; (6):  01 80 00 1c 02 1f

row#2[7997] flag: ------, lock: 0, len=13

col 0; len 3; (3):  c2 06 2e

這個問題希望大家共同探討。。。。

索引的update和delete操作就是這樣的,下面我們來做索引的分裂實驗。

SQL> create table test2(id number,name varchar2(100));

表已創建。

SQL> create index idx_test2 on test2(id);

索引已創建。

SQL> declare

  2       begin

  3          for i in 1..540

  4

  5             loop

  6

  7             insert into test2 values (i,'100');

  8            commit;

  9            end loop ;

 10

 11         end;

 12

 13          /

SQL> select file_id,extent_id,block_id from dba_extents where segment_name='IDX_TEST2' ;

   FILE_ID  EXTENT_ID   BLOCK_ID
---------- ---------- ----------
         6          0        105

轉儲105

Block header dump:  0x0180006c
 Object id on Block? Y
 seg/obj: 0xcd7a  csc: 0x00.c9631  itc: 2  flg: E  typ: 2 - INDEX
     brn: 0  bdba: 0x1800069 ver: 0x01 opc: 0
     inc: 0  exflg: 0
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000
0x02   0x0008.002.000001bd  0x00800603.0124.0c  --U-    1  fsc 0x0000.000c9632
 
Leaf block dump
===============
header address 152314468=0x9142264
kdxcolev 0
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 2
kdxcosdc 0
kdxconro 500
kdxcofbo 1036=0x40c
kdxcofeo 1640=0x668
kdxcoavs 604
kdxlespl 0
kdxlende 0
kdxlenxt 0=0x0
kdxleprv 0=0x0
kdxledsz 0
kdxlebksz 8036
row#0[8024] flag: ------, lock: 0, len=12
col 0; len 2; (2):  c1 02
col 1; len 6; (6):  01 80 00 5c 00 00
row#1[8012] flag: ------, lock: 0, len=12
col 0; len 2; (2):  c1 03
col 1; len 6; (6):  01 80 00 5c 00 01

此時root和leaf在同一個塊中。

SQL> insert into test2 values(5,'100');

已創建 1 行。


發生了分裂

Block header dump:  0x0180006c

 Object id on Block? Y

 seg/obj: 0xcd7e  csc: 0x00.c9fb3  itc: 1  flg: E  typ: 2 - INDEX

     brn: 0  bdba: 0x1800069 ver: 0x01 opc: 0

     inc: 0  exflg: 0

 

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc

0x01   0x000a.017.00000194  0x008000a0.011a.01  -BU-    1  fsc 0x0000.000c9fb5

 

Branch block dump

=================

header address 152314444=0x914224c

kdxcolev 1

KDXCOLEV Flags = - - -

kdxcolok 1

kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y

kdxconco 2

kdxcosdc 1

kdxconro 1

kdxcofbo 30=0x1e

kdxcofeo 8051=0x1f73

kdxcoavs 8021

kdxbrlmc 25165935=0x180006f

kdxbrsno 0

kdxbrbksz 8060 

kdxbr2urrc 0

row#0[8051] dba: 25165936=0x1800070

col 0; len 3; (3):  c2 03 50      --349

col 1; TERM

----- end of branch block dump -----

End dump data blocks tsn: 7 file#: 6 minblk 108 maxblk 108


SQL> select getbfno('0x1800070') from dual;

GETBFNO('0X1800070')

-----------------------------------------------

datafile# is:6

datablock is:112


此時我們分別轉儲111和112數據文件



111文件如下


Leaf block dump

===============

header address 152314468=0x9142264

kdxcolev 0

KDXCOLEV Flags = - - -

kdxcolok 0

kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y

kdxconco 2

kdxcosdc 1

kdxconro 279

kdxcofbo 594=0x252

kdxcofeo 4499=0x1193

kdxcoavs 3917

kdxlespl 0

kdxlende 0

kdxlenxt 25165936=0x1800070

kdxleprv 0=0x0

kdxledsz 0

kdxlebksz 8036

row#0[4511] flag: ------, lock: 0, len=12

col 0; len 2; (2):  c1 02

col 1; len 6; (6):  01 80 00 5c 00 00

row#1[4523] flag: ------, lock: 0, len=12

col 0; len 2; (2):  c1 03

col 1; len 6; (6):  01 80 00 5c 00 01

row#2[4535] flag: ------, lock: 0, len=12

col 0; len 2; (2):  c1 04

col 1; len 6; (6):  01 80 00 5c 00 02

row#3[4547] flag: ------, lock: 0, len=12

col 0; len 2; (2):  c1 05

col 1; len 6; (6):  01 80 00 5c 00 03

row#4[4559] flag: ------, lock: 0, len=12

col 0; len 2; (2):  c1 06  --引起了拆分

col 1; len 6; (6):  01 80 00 5c 00 04

row#5[4499] flag: ------, lock: 2, len=12

col 0; len 2; (2):  c1 06

col 1; len 6; (6):  01 80 00 5c 02 1c

row#6[4583] flag: ------, lock: 0, len=12

col 0; len 2; (2):  c1 07

col 1; len 6; (6):  01 80 00 5c 00 05

row#7[4595] flag: ------, lock: 0, len=12

col 0; len 2; (2):  c1 08


。。。。。。。


row#278[8023] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 03 4f
col 1; len 6; (6):  01 80 00 5c 01 15
----- end of leaf block dump -----
End dump data blocks tsn: 7 file#: 6 minblk 111 maxblk 111

轉儲112塊
Block header dump:  0x01800070
 Object id on Block? Y
 seg/obj: 0xcd7e  csc: 0x00.c9fb4  itc: 2  flg: E  typ: 2 - INDEX
     brn: 0  bdba: 0x1800069 ver: 0x01 opc: 0
     inc: 0  exflg: 0
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x000a.017.00000194  0x00800131.011b.02  -B--    1  fsc 0x0000.00000000
0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000
 
Leaf block dump
===============
header address 152314468=0x9142264
kdxcolev 0
KDXCOLEV Flags = - - -
kdxcolok 1
kdxcoopc 0x87: opcode=7: iot flags=--- is converted=Y
kdxconco 2
kdxcosdc 1
kdxconro 262
kdxcofbo 560=0x230
kdxcofeo 4633=0x1219
kdxcoavs 4073
kdxlespl 0
kdxlende 0
kdxlenxt 0=0x0
kdxleprv 25165935=0x180006f
kdxledsz 0
kdxlebksz 8036
row#0[4633] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 03 50
col 1; len 6; (6):  01 80 00 5c 01 16
row#1[4646] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 03 51
col 1; len 6; (6):  01 80 00 5c 01 17
row#2[4659] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 03 52
col 1; len 6; (6):  01 80 00 5c 01 18
row#3[4672] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 03 53
col 1; len 6; (6):  01 80 00 5c 01 19
row#4[4685] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 03 54
col 1; len 6; (6):  01 80 00 5c 01 1a
row#5[4698] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 03 55
col 1; len 6; (6):  01 80 00 5c 01 1b
。。。。。。
row#256[7958] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 06 24
col 1; len 6; (6):  01 80 00 5c 02 16
row#257[7971] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 06 25
col 1; len 6; (6):  01 80 00 5c 02 17
row#258[7984] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 06 26
col 1; len 6; (6):  01 80 00 5c 02 18
row#259[7997] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 06 27
col 1; len 6; (6):  01 80 00 5c 02 19
row#260[8010] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 06 28
col 1; len 6; (6):  01 80 00 5c 02 1a
row#261[8023] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 06 29
col 1; len 6; (6):  01 80 00 5c 02 1b
----- end of leaf block dump -----
End dump data blocks tsn: 7 file#: 6 minblk 112 maxblk 112
每個塊大約各佔一半,這就是所謂的50-50拆分

下面我們看看99-1拆分

當我們給表中插入從最大值開始遞增的值,當插入值754時,發生了分裂,108塊中的信息如下:增加了0x1800071
row#0[8051] dba: 25165936=0x1800070
col 0; len 3; (3):  c2 03 50
col 1; TERM
row#1[8042] dba: 25165937=0x1800071 -113
col 0; len 3; (3):  c2 08 55
col 1; TERM
----- end of branch block dump -----
End dump data blocks tsn: 7 file#: 6 minblk 108 maxblk 108

我們轉儲113塊的內容
row#0[8023] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 08 55
col 1; len 6; (6):  01 80 00 5f 00 f6
發現只含有最後的值,沒有想5050分裂那樣,各有一半。

以上就是關於b*樹索引的學習,參照了別人很多的東西,感覺自己受益匪淺。其中還有一些錯誤或者疑問需要大家共同探討。。。



發佈了26 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章