Oracle的B* Tree 索引

1. B-trees, short for balanced trees, are the most common type of database index.顾名思义,B-Tree是一个平衡树的结构【注意这里的B表示Balanced平衡的意思,而不是Binary二叉】,B树索引也是Oracle里最为常见的索引类型。B树索引里的数据是已经按照键字或者是被索引字段事先排好序存放的,默认是升序存放,也解释了为什么我们在创建索引的时候有可能会用到数据库的临时表空间的临时段。

索引作为一种可选的数据结构,你可以选择为某个表里的创建索引,也可以不创建。这是因为一旦创建了索引,就意味着Oracle对表进行DML(包括INSERT、UPDATE、DELETE)时,必须处理额外的工作量(也就是对索引结构的维护)以及存储方面的开销。所以创建索引时,需要考虑创建索引所带来的查询性能方面的提高,与引起的额外的开销相比,是否值得。

B树索引内部结构
B树索引是一个典型的树结构,其包含的组件主要是:
1) 叶子节点(Leaf node):数据行的键值(key value)、键值对应数据行的 ROWID。
2) 分支节点(Branch node):最小的键值前缀(minimum key prefix),用于在(本块的)两个键值之间做出分支选择,指向包含所查找键值的子块(child block)的指针()所有的 键值-ROWID 对(key and ROWID pair)都与其左右的兄弟节点(sibling)向链接(link),并按照(key,ROWID)的顺序排序
3) 根节点(Root node):一个B树索引只有一个根节点,它实际就是位于树的最顶端的分支节点。

图一

图二

A: 对于分支节点块(包括根节点块)来说,其所包含的索引条目都是按照顺序排列的(缺省是升序排列,也可以在创建索引时指定为降序排列)。每个索引条目(也可以叫做每条记录)都具有两个字段。第一个字段表示当前该分支节点块下面所链接的索引块中所包含的最小键值;第二个字段为四个字节,表示所链接的索引块的地址,该地址指向下面一个索引块。在一个分支节点块中所能容纳的记录行数由数据块大小以及索引键值的长度决定。比如从上图一可以看到,对于根节点块来说,包含三条记录,分别为(0 B1)、(500 B2)、(1000 B3),它们指向三个分支节点块。其中的0、500和1000分别表示这三个分支节点块所链接的键值的最小值。而B1、B2和B3则表示所指向的三个分支节点块的地址。

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

C: 知道这些信息以后,我们可以举个例子来说明如何估算每个索引能够包含多少条目,以及对于表来说,所产生的索引大约多大。对于每个索引块来说,缺省的PCTFREE为10%,也就是说最多只能使用其中的90%。同时9i以后,这90%中也不可能用尽,只能使用其中的87%左右。也就是说,8KB的数据块中能够实际用来存放索引数据的空间大约为6488(8192×90%×88%)个字节。

D: 假设我们有一个非分区表,表名为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
注意:在Oracle中null被定义为无限大,且null不等于null,故在索引不会存有与null值对应的条目。如果不加其他限制条件的对表进行is null扫描,将会是全表扫描,如果是is not null扫描将会是全索引扫描。

参考资料:

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

http://czmmiao.diandian.com/post/2012-04-09/19078436

http://blog.csdn.net/v_JULY_v/article/details/6530142


你可能感兴趣的:(数据库)