B*树索引

 B树索引是一个典型的树结构,其包含的组件主要是:

1)      叶子节点(Leaf node):包含条目直接指向表里的数据行。

2)      分支节点(Branch node):包含的条目指向索引里其他的分支节点或者是叶子节点。

3)      根节点(Root node):一个B树索引只有一个根节点,它实际就是位于树的最顶端的分支节点。

对于分支节点块(包括根节点块)来说,其所包含的索引条目都是按照顺序排列的(缺省是升序排列,也可以在创建索引时指定为降序排列)。每个索引条目(也可以叫做每条记录)都具有两个字段。第一个字段表示当前该分支节点块下面所链接的索引块中所包含的最小键值;第二个字段为四个字节,表示所链接的索引块的地址,该地址指向下面一个索引块。在一个分支节点块中所能容纳的记录行数由数据块大小以及索引键值的长度决定。

对于叶子节点块来说,其所包含的索引条目与分支节点一样,都是按照顺序排列的(缺省是升序排列,也可以在创建索引时指定为降序排列)。每个索引条目(也可以叫做每条记录)也具有两个字段。第一个字段表示索引的键值,对于单列索引来说是一个值;而对于多列索引来说则是多个值组合在一起的。第二个字段表示键值所对应的记录行的ROWID,该ROWID是记录行在表里的物理地址。

使用B树索引的情形:
1、索引用于访问表中的行:通过读索引来访问表中的行。此时你希望访问表中很少的一部分行(只占一个很小的百分比)。
2、索引用于回答一个查询:索引包含了足够的信息来回答整个查询,我根本不用去访问表。在这种情况下,索引则用作一个“较瘦“版本的表,即通过查询索引就能找到查询结果,在这种情况下,可以通过处理标准100%的数据,而不像第一种情况中只能访问少量的数据。

Oracle在表示从索引根块到叶子块遍历所涉及的块数时用了两个含义稍微不同的术语。height,指从根块到叶子块遍历所需要的块数。使用analyze index validate structure 命令分析索引后,可以从index_stats视图找到这个高度(height)。另一个术语是blevel,指分支层数(不把叶子块层算在内),比height小1。注意,analyze index命令有一个坏处,就是在运行过程中,会锁定整个表,从而阻塞其他session对表进行插入、更新和删除等操作。这是因为该命令的主要目的并不是用来填充index_stats视图的,其主要作用在于校验索引中的每个有效的索引条目都对应到表里的一行,同时表里的每一行数据在索引中都存在一个对应的索引条目。为了完成该目的,所以在运行过程中要锁定整个表,同时对于很大的表来说,运行该命令需要耗费非常多的时间。
另外,直接运行ANALYZE INDEX … VALIDATE STRUCTURE命令,然后检查index_stats视图的pct_used字段,如果该字段过低(低于50%),则说明存在碎片。

在视图index_stats中,height表示B树索引的高度;blocks表示分配了的索引块数,包括还没有被使用的;pct_used表示当前索引中被使用了的空间的百分比。其值是通过该视图中的(used_space/btree_space)*100计算而来。used_space表示已经使用的空间,而btree_space表示索引所占的总空间;del_lf_rows表示被删除的记录行数(表里的数据被删除并不会立即将其对应于索引里的索引条目清除出索引块);del_lf_rows_len表示被删除的记录所占的总空间;lf_rows表示索引中包含的总记录行数,包括已经被删除的记录行数。这样的话,索引中未被删除的记录行数就是lf_rows-del_lf_rows。同时我们可以计算未被删除的记录所对应的索引条目(也就是有效索引条目)所占用的空间为((used_space – del_lf_rows_len) / btree_space) * 100。

索引按索引键的顺序存储。索引会按键的有序顺序进行访问。索引指向的块则随机地存储在堆中。因此,我们通过索引访问表时,会执行大量分散、随机的I/O。我们将以一种非常随意的方式读取和重新读取块。这种块I/O可能非常慢。

索引键压缩
可以通过index_stats中的opt_cmpr_count列来得出最佳压缩数,opt_cmpr_pctsave则指出可以得到多大的节省幅度。利用压缩,块缓冲区缓存放以前存在更多的索引条目,缓存命中率可能上升,物理IO应该会下降,但是要多占用一些CPU时间来处理索引,还会增加块竞争的可能性。如果现在已经在大量的占用CPU时间,再增加压缩键索引只能适得其反,这会减慢处理速度。另一方面,如果目前IO操作很多,使用压缩键索引就能加快处理速度。


内部结构
JEL@JEL >create table test as select * from all_objects;

Table created.

JEL@JEL >create index i_test on test(object_id);

Index created.

JEL@JEL >select object_id from user_objects where object_name ='I_TEST';

 OBJECT_ID
----------
      9939

JEL@JEL >alter session set events 'immediate trace name treedump level 9939';

Session altered.

索引转储文件如下:
----- begin tree dump
branch: 0x100010f 16777487 (0: nrow: 20, level: 1)
   leaf: 0x1000110 16777488 (-1: nrow: 485 rrow: 485)
   leaf: 0x1000111 16777489 (0: nrow: 479 rrow: 479)
   leaf: 0x1000112 16777490 (1: nrow: 479 rrow: 479)
   leaf: 0x1000113 16777491 (2: nrow: 479 rrow: 479)
 .......................................................
   leaf: 0x1000122 16777506 (17: nrow: 478 rrow: 478)
   leaf: 0x1000123 16777507 (18: nrow: 237 rrow: 237)
----- end tree dump

其中,每一行的第一列表示节点类型:branch表示分支节点(包括根节点),而leaf则表示叶子节点;第二列表示十六进制表示的节点的地址;第三列表示十进制表示的节点的地址;第四列表示相对于前一个节点的位置,根节点从0开始计算,其他分支节点和叶子节点从-1开始计算;第五列的nrow表示当前节点中所含有的索引条目的数量。第六列中的rrow表示有效的索引条目(因为索引条目如果被删除,不会立即被清除出索引块中。所以nrow减rrow的数量就表示已经被删除的索引条目数量)的数量.

转储根节点:
JEL@JEL >select dbms_utility.data_block_address_file(16777487),dbms_utility.data_block_address_block(16777487) from dual;

DBMS_UTILITY.DATA_BLOCK_ADDRESS_FILE(16777487)
----------------------------------------------
DBMS_UTILITY.DATA_BLOCK_ADDRESS_BLOCK(16777487)
-----------------------------------------------
                                             4
                                            271

转储文件:
Branch block dump
=================
header address 217162828=0xcf1a44c
kdxcolev 1
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 2
kdxcosdc 0
kdxconro 19
kdxcofbo 66=0x42
kdxcofeo 7885=0x1ecd
kdxcoavs 7819
kdxbrlmc 16777488=0x1000110
kdxbrsno 0
kdxbrbksz 8056
kdxbr2urrc 1
row#0[8047] dba: 16777489=0x1000111
col 0; len 3; (3):  c2 06 23
col 1; TERM
row#1[8038] dba: 16777490=0x1000112
col 0; len 3; (3):  c2 0b 38
col 1; TERM
.........................................
row#18[7885] dba: 16777507=0x1000123
col 0; len 3; (3):  c2 60 46
col 1; TERM
----- end of branch block dump -----
End dump data blocks tsn: 4 file#: 4 minblk 271 maxblk 271

其中的kdxcolev表示索引层级号,对叶子节点来说该值为0;kdxcolok表示该索引上是否正在发生修改块结构的事务;kdxcoopc表示内部操作代码;kdxconco表示索引条目中列的数量;kdxcosdc表示索引结构发生变化的数量,当你修改表里的某个索引键值时,该值增加;kdxconro表示当前索引节点中索引条目的数量,但是注意,不包括kdxbrlmc指针;kdxcofbo表示当前索引节点中可用空间的起始点相对当前块的位移量;kdxcofeo表示当前索引节点中可用空间的最尾端的相对当前块的位移量;kdxcoavs表示当前索引块中的可用空间总量,也就是用kdxcofeo减去kdxcofbo得到的。kdxbrlmc表示分支节点的地址,该分支节点存放了索引键值小于row#0(在转储文档后半部分显示)所含有的最小值的所有节点信息;kdxbrsno表示最后一个被修改的索引条目号,这里看到是0,表示该索引是新建的索引;kdxbrbksz表示可用数据块的空间大小。实际从这里已经可以看到,即便是PCTFREE设置为0,也不能用足8192字节。

col 0表示该分支节点所链接的最小键值,col 1表示所链接的分支节点的地址,该值经过一定的转换以后实际就是row#所在行的dba的值,这里,根节点下没有其他的分支节点,则col 1为TERM;


对row#0[8047] dba: 16777489=0x1000111进行转储:

文件如下:
Leaf block dump
===============
header address 217162852=0xcf1a464
kdxcolev 0
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 2
kdxcosdc 0
kdxconro 479
kdxcofbo 994=0x3e2
kdxcofeo 1810=0x712
kdxcoavs 816
kdxlespl 0
kdxlende 0
kdxlenxt 16777490=0x1000112
kdxleprv 16777488=0x1000110
kdxledsz 0
kdxlebksz 8032
row#0[8019] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 06 23
col 1; len 6; (6):  01 00 00 14 00 30
............................................
row#478[1810] flag: ------, lock: 0, len=13
col 0; len 3; (3):  c2 0b 37
col 1; len 6; (6):  01 00 00 1a 00 28
----- end of leaf block dump -----
End dump data blocks tsn: 4 file#: 4 minblk 273 maxblk 273


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

1)  每次读取一个数据块(通过等待事件“db file sequential read”体现出来)。访问索引里的数据块

2)  每次读取多个数据块(通过等待事件“db file scattered read”体现出来)。I/O操作属于全表扫描

等待事件主要根据实际获取物理I/O块的方式来命名的,而不是根据其在I/O子系统的逻辑方式来命名的.在索引上访问数据块时,会对应到db file sequential read等待事件,其根源在于我们是按照顺序从一个索引块跳到另一个索引块,从而找到最终的索引块的。全表扫描时,oracle知道要访问所有的数据块,因此唯一的问题就是尽可能高效的访问这些数据块。因此,这时oracle可以采用同步的方式,分几批,同时获取多个数据块。这几批的数据块在物理上可能是分散在表里的,因此其对应到db file scattered read等待事件。

物理组织
如果想要的行通常位于相同的块上,在这种情况下,即使要访问大量的行(占很大的百分比),索引区间扫描可能也很有用。原因在于:我们需要读取和重新读取的数据库块很可能会被缓存,因为数据共同放置在同一个位置(co-located)。另一方面,如果行并非共同存储在一个位置上,使用这个索引对性能来讲可能就是灾难性的。

聚簇因子
index_stats中clustering_factor列,如果这个值与块数很接近,则说明表相当有序。同一个叶子块中额索引条目可能指向同一个数据块上的行。如果这个值与行数接近,则说明表的次序可能就是非常随机的。同一个叶子块上的索引条目不太可能指向同一个数据块的行。当Oracle对索引结构执行区间扫描时,如果它发现索引中的下一行几乎总与前一行在同一个数据库块上,就不会再执行另一个I/O从缓冲区缓存中获得表块。它已经有表块的一个句柄,只需直接使用就可以了。不过,如果下一行不在同一个块上,就会释放当前的这个块,而执行另一个I/O从缓冲区缓存获取要处理的下一个块。对于一个表来说,一般只有一个索引能有合适的聚簇因子!表中的行可能只以一种方式排序。如果你认为数据物理聚簇很重要,可以考虑使用一个IOT、B*树聚簇,或者在连续地重建表时考虑散列聚簇。
可以把聚簇因子看做是通过索引读取整个表时对表执行的逻辑IO次数。

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

转载于:http://blog.itpub.net/29337971/viewspace-1063408/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值