教你如何成为Oracle 10g OCP - 第九章 对象管理(6) - B树索引的访问

9.2.2  B树索引的访问


我们已经知道了B树索引的体系结构,那么当oracle需要访问索引里的某个索引条目时,
oracle是如何找到该索引条目所在的数据块的呢?

当oracle进程需要访问数据文件里的数据块时,oracle会有两种类型的I/O操作方式:

A. 顺序访问,每次读取一个数据块(等待事件“db file sequential read 文件顺序读取”)。
B. 随机访问,每次读取多个数据块(等待事件“db file scattered  read 文件分散读取”)。

第一种方式则是访问索引里的数据块,而第二种方式的I/O操作属于全表扫描。

参考常见等待事件:
http://space.itpub.net/35489/viewspace-84652 

-------------------------------------------------------------------------------
备注:

db file scattered read (文件分散读取)
这种情况通常显示与全表扫描相关的等待。当数据库进行全表扫时,基于性能的考虑,数
据会分散(scattered)读入Buffer Cache。如果这个等待事件比较显著,可能说明对于某些
全表扫描的表,没有创建索引或者没有创建合适的索引,我们可能需要检查这些数据表已
确定是否进行了正确的设置。 当这个等待事件比较显著时,可以结合v$session_longops
动态性能视图来进行诊断,该视图中记录了长时间(运行时间超过6秒的)运行的事物,
可能很多是全表扫描操作

db file sequential read (文件顺序读取)
这一事件通常显示与单个数据块相关的读取操作(如索引读取)。如果这个等待事件比较
显著,可能表示在多表连接中,表的连接顺序存在问题,可能没有正确的使用驱动表;
或者可能说明不加选择地使用了索引。

在大多数情况下我们说,通过索引可以更为快速的获取记录,所以对于一个编码规范、
调整良好的数据库,这个等待很大是很正常的。但是在很多情况下,使用索引并不是最
佳的选择,比如读取较大表中大量的数据,全表扫描可能会明显快于索引扫描,所以在
开发中我们就应该注意,对于这样的查询应该进行避免使用索引扫描。

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

 


我们看到前面对B树索引的体系结构的描述,可以知道其为一个树状的立体结构。其对
应到数据文件里的排列当然还是一个平面的形式,也就是像下面这样。因此,当oracle
需要访问某个索引块的时候,势必会在这个结构上跳跃的移动。

/根/分支/分支/叶子/…/叶子/分支/叶子/叶子/…/叶子/分支/叶子/叶子/…/叶子/分支/.....

当oracle需要获得一个索引块时,首先从根节点开始,根据所要查找的键值,从而知道
其所在的下一层的分支节点,然后访问下一层的分支节点,再次同样根据键值访问再下
一层的分支节点,如此这般,最终访问到最底层的叶子节点。可以看出,其获得物理
I/O块时,是一个接着一个,按照顺序,串行进行的。在获得最终物理块的过程中,我们
不能同时读取多个块,因为我们在没有获得当前块的时候是不知道接下来应该访问哪个
块的。因此,在索引上访问数据块时,会对应到db file sequential read等待事件,其
根源在于我们是按照顺序从一个索引块跳到另一个索引块,从而找到最终的索引块的。

那么对于全表扫描来说,则不存在访问下一个块之前需要先访问上一个块的情况。全表
扫描时,oracle知道要访问所有的数据块,因此唯一的问题就是尽可能高效的访问这些
数据块。因此,这时oracle可以采用同步的方式,分几批,同时获取多个数据块。这几
批的数据块在物理上可能是分散在表里的,因此其对应到db file scattered read等待
事件。

 


9.2.2.1  B树索引的对于插入(INSERT)的管理  

对于B树索引的插入情况的描述,可以分为两种情况:一种是在一个已经充满了数据
的表上创建索引时,索引是怎么管理的;另一种则是当一行接着一行向表里插入或更
新或删除数据时,索引是怎么管理的。

      对于第一种情况来说,比较简单。当在一个充满了数据的表上创建索引(create
index命令)时,oracle会先扫描表里的数据并对其进行排序,然后生成叶子节点。生
成所有的叶子节点以后,根据叶子节点的数量生成若干层级的分支节点,最后生成根
节点。这个过程是很清晰的。

      但是对于第二种情况来说,会复杂很多。我们结合一个例子来说明。为了方便
起见,我们在一个数据块为2KB的表空间上创建一个测试表,并为该表创建一个索引,
该索引同样位于2KB的表空间上。

SQL> create table index_test(id char(150)) tablespace tbs_2k;
SQL> create index idx_test on index_test(id) tablespace tbs_2k;

      当一开始在一个空的表上创建索引的时候,该索引没有根节点,只有一个叶子
节点。我们以树状形式转储上面的索引idx_test。

SQL> select object_id from user_objects where object_name='IDX_TEST';

OBJECT_ID
----------
     7390

SQL> alter session set events 'immediate trace name treedump level 7390';

从转储文件可以看到,该索引中只有一个叶子节点(leaf)。

----- begin tree dump
leaf: 0x1c001a2 29360546 (0: nrow: 0 rrow: 0)
----- end tree dump

      随着数据不断被插入表里,该叶子节点中的索引条目也不断增加,当该叶
子节点充满了索引条目而不能再放下新的索引条目时,该索引就必须扩张,必须
再获取一个可用的叶子节点。这时,索引就包含了两个叶子节点,但是两个叶子
节点不可能单独存在的,这时它们两必须有一个上级的分支节点,其实这也就是
根节点了。于是,我们的索引应该具有3个索引块,一个根节点,两个叶子节点。


我们来做个试验看看这个过程。我们先试着插入10条记录。注意,对于2KB的索引
块同时PCTFREE为缺省的10%来说,只能使用其中大约1623字节(2048×90%×88%)。
对于表index_test来说,叶子节点中的每个索引条目所占的空间大约为161个字节
(3个字节行头+1个字节列长+150个字节列本身+1个字节列长+6个字节ROWID),那
么当我们插入将10条记录以后,将消耗掉大约1610个字节。

SQL> begin
 2    for i in 1..10 loop
 3        insert into index_test values (rpad(to_char(i*2),150,'a'));
 4    end loop;
 5 end;
 6 /

SQL> commit;

SQL> select file_id,block_id,blocks from dba_extents where segment_name='IDX_TEST';

  FILE_ID  BLOCK_ID    BLOCKS
---------- ---------- ----------
        7       417        32

SQL> alter system dump datafile 7 block 418;
     --因为第一个块为块头,不含数据,所以转储第二个块。

      打开跟踪文件以后,如下所示,可以发现418块仍然是一个叶子节点,包
含10个索引条目,该索引块还没有被拆分。注意其中的kdxcoavs为226,说明可用
空间还剩226个字节,说明还可以插入一条记录。之所以与前面计算出来的只能放
10条记录有出入,是因为可用的1623字节只是一个估计值。

……
kdxcoavs 226
……

row#0[1087] flag: -----, lock: 0
col 0; len 150; (150):
 31 30 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
col 1; len 6; (6): 01 c0 01 82 00 04

row#1[926] flag: -----, lock: 0

……

      接下来,我们再次插入一条记录,以便基本充满该叶子节点,使得剩下的可
用空间不足以再插入一条新的条目。如下所示。

SQL> insert into index_test values(rpad(to_char(11*2),150,'a'));

      这个时候我们再次转储418块以后会发现与前面转储的内容基本一致,只是
又增加了一个索引条目。而这个时候,如果向表里再次插入一条新的记录的话,该
叶子节点(418块)必须进行拆分。

SQL> insert into index_test values(rpad(to_char(12*2),150,'a'));
SQL> alter system dump datafile 7 block 418;

      转储出418块以后,我们会发现,该索引块从叶子节点变成了根节点(kdxcolev为1,
同时row#0部分的col 1为TERM表示根节点下没有其他分支节点)。这也就说明,当第一个
叶子节点充满以后,进行分裂时,先获得两个可用的索引块作为新的叶子节点,然后将当
前该叶子节点里所有的索引条目拷贝到这两个新获得的叶子节点,最后将原来的叶子节点
改变为根节点。

……
kdxcolev 1
……
kdxbrlmc 29360547=0x1c001a3
……
row#0[1909] dba: 29360548=0x1c001a4
col 0; len 1; (1): 34
col 1; TERM
----- end of branch block dump -----

      同时,从上面的kdxbrlmc和row#0中的dba可以知道,该根节点分别指向29360547
和29360548两个叶子节点。我们分别对这两个叶子节点进行转储看看里面放了些什么。

SQL> select dbms_utility.data_block_address_file(29360547),
 2 dbms_utility.data_block_address_block(29360547) from dual;

DBMS_UTILITY.DATA_BLOCK_ADDRES DBMS_UTILITY.DATA_BLOCK_ADDRES
------------------------------ ------------------------------
                            7                           419

SQL> select dbms_utility.data_block_address_file(29360548),
 2 dbms_utility.data_block_address_block(29360548) from dual;

DBMS_UTILITY.DATA_BLOCK_ADDRES DBMS_UTILITY.DATA_BLOCK_ADDRES
------------------------------ ------------------------------
                            7                           420

SQL> alter system dump datafile 7 block 419;
SQL> alter system dump datafile 7 block 420;

在打开跟踪文件之前,我们先来看看表index_test里存放了哪些数据。   

SQL> select substr(id,1,2) from index_test order by substr(id,1,2);

SUBSTR(ID,1,2)
--------------
10
12
14
16
18
20
22
24
2a
4a
6a
8a

打开419块的跟踪文件可以发现,里面存放了10、12、14、16、18、20、22、24
和2a;而420块的跟踪文件中记录了4a、6a和8a。也就是说,由于最后我们插入
24的缘故,导致整个叶子节点发生分裂,从而将10、12、14、16、18、20、22、
和2a放到419块里,而4a、6a和8a则放入420块里。然后,再将新的索引条目(24)
插入对应的索引块里,也就是419块。

假如我们再最后不是插入12*2,而是插入9会怎么样?我们重新测试一下,返回
到index_test里有11条记录的情况下,然后我们再插入9 。

SQL> insert into index_test values (rpad('9',150,'a'));

      这个时候,418块还是和原来一样变成了根节点,同时仍然生成出了2个叶
子节点块,分别是419和420。但是有趣的是,419块里的内容与在插入9之前的叶
子节点(当时的418块)的内容完全相同,而420块里则只有一个索引条目,也就
是新插入的9。这也就是说,由于最后我们插入9的缘故,导致整个叶子节点发生
分裂。但是分裂过程与插入12*2的情况是不一样的,这时该叶子节点的内容不进
行拆分,而是直接完全拷贝到一个新的叶子节点(419)里,然后将新插入的9放入
另外一个新的叶子节点(420)。我们应该注意到,插入的这个9是表里所有记录
里的最大字符串。

如果这时,我们再次插入12*2,则会发现419号节点的分裂过程和前面描述的一样,
会将原来放在419块里的4a、6a和8a放入一个新的叶子节点里(421块),然后将
12*2放入419块,于是这个时候419块所含有的索引条目为10、12、14、16、18、
20、22、和2a。同时420块没有发生变化。

 

      根据上面的测试结果,我们可以总结一下叶子节点的拆分过程。这个过程需
要分成两种情况,一种是插入的键值不是最大值;另一种是插入的键值是最大值。

      对于第一种情况来说,当一个非最大键值要进入索引,但是发现所应进入的
索引块不足以容纳当前键值时:

1) 从索引可用列表上获得一个新的索引数据块。
2) 将当前充满了的索引中的索引条目分成两部分,一部分是具有较小键值的,
另一部分是具有较大键值的。Oracle会将具有较大键值的部分移入新的索引数
据块,而较小键值的部分保持不动。
3) 将当前键值插入合适的索引块中,可能是原来空间不足的索引块,也可能
是新的索引块。
4) 更新原来空间不足的索引块的kdxlenxt信息,使其指向新的索引块。
5) 更新位于原来空间不足的索引块右边的索引块里的kdxleprv,使其指向新的索引块。
6) 向原来空间不足的索引块的上一级的分支索引块中添加一个索引条目,该索引
条目中保存新的索引块里的最小键值,以及新的索引块的地址。

从上面有关叶子节点分裂的过程可以看出,其过程是非常复杂的。因此如果发生
的是第二种情况,则为了简化该分裂过程,oracle省略了上面的第二步,而是直接
进入第三步,将新的键值插入新的索引块中。

    在上例中,当叶子节点越来越多,导致原来的根节点不足以存放新的索引条目
(这些索引条目指向叶子节点)时,则该根节点必须进行分裂。当根节点进行分裂时:

1) 从索引可用列表上获得两个新的索引数据块。
2) 将根节点中的索引条目分成两部分,这两部分分别放入两个新的索引块,从而形
成两个新的分支节点。
3) 更新原来的根节点的索引条目,使其分别指向这两个新的索引块。


因此,这时的索引层次就变成了2层。同时可以看出,根节点索引块在物理上始终
都是同一个索引块。而随着数据量的不断增加,导致分支节点又要进行分裂。分支
节点的分裂过程与根节点类似(实际上根节点分裂其实是分支节点分裂的一个特例
而已):

1) 从索引可用列表上获得一个新的索引数据块。
2) 将当前满了的分支节点里的索引条目分成两部分,较小键值的部分不动,而较大
键值的部分移入新的索引块。
3) 将新的索引条目插入合适的分支索引块。
4) 在上层分支索引块中添加一个新的索引条目,使其指向新加的分支索引块。

当数据量再次不断增加,导致原来的根节点不足以存放新的索引条目(这些索引条目
指向分支节点)时,再次引起根节点的分裂,其分裂过程与前面所说的由于叶子节点
的增加而导致的根节点分裂的过程是一样的。

同时,根节点分裂以后,索引的层级再次递增。由此可以看出,根据B树索引的分裂
机制,一个B树索引始终都是平衡的。注意,这里的平衡是指每个叶子节点与根节点
的距离都是相同的。同时,从索引的分裂机制可以看出,当插入的键值始终都是增大
的时候,索引总是向右扩展;而当插入的键值始终都是减小的时候,索引则总是向左
扩展。

 

B-Tree索引结构参考:

http://space.itpub.net/?uid-9842-action-viewspace-itemid-324586

http://www.cublog.cn/u3/112761/showart_2218897.html

http://space.itpub.net/?uid-9842-action-viewspace-itemid-324139

http://www.360doc.com/content/11/0120/09/5547270_87766334.shtml

http://www.itpub.net/thread-300772-1-1.html

http://www.examda.com/oracle/zhonghe/20080505/110913194.html
http://**/viewthread.php?tid=21520 

深入研究Oracle B树索引系列 -by hanson

 

..... 待续......


 

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/35489/viewspace-687192/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/35489/viewspace-687192/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值