Oracle Database 中 B*Tree 索引内部维护

最近一周在复习索引相关的东西,除了回顾concept,还在MOS上看到了一篇比较好的文档。分享给大家。

文档编号:[ID 30405.1]


This article is only concerned with B*tree indexes which are currently the most commonly used.  The theory of B*tree indexes is beyond the scope of this article; for more  information refer to computer science texts dealing with data structures.

这篇文档只描述关于当前最常用的b*tree索引。b*tree索引的原理已经超过了本文档的范围。更多的信息可以去查看计算机的数据结构原理。

Format of Index Blocks
~~~~~~~~~~~~~~~~~~~~~~

索引的结构(格式?)


Within a B*tree index, index blocks are either branch blocks, the upper blocks  within the B*tree index, or leaf blocks, the lowest level index blocks. Branch blocks contain  index data that point to lower level index blocks. Leaf blocks contain every indexed data value and a corresponding ROWID used to locate the actual row.


在b*tree索引中,一共有两种索引块,一种是branch block(分支块),还有leaf block(叶块),一种是高level,一种低level的(低level,在索引底部)。branch block包含定位低等级的索引块(可能是branch block或者leaf block)的pointer leaf block包含每一个索引数据值和相应的rowid(用来定位真正的row)。

以下是一个索引块的分布情况:


               Index Block Format


|-----------------------------------------------------|
|                                                     |
|            Index Block Header             |
|                                                     |
------------------------------------------------------|
|                                                     |
|  Space reserved for future updates   |
|  and inserts of new rows with the     |
|   appropriate key values                  |
|                                                     |
|-----------------------------------------------------| <- PCTFREE say 10
|                                                     |
|             Index Key Data                  |
|                                                     |
|                                                     |
|                                                     |
|                                                     |
|                                                     |
|                                                     |
|                                                     |
|                                                     |
|                                                     |
|-----------------------------------------------------|


B*tree Index Creation

创建b*tree索引


~~~~~~~~~~~~~~~~~~~~~
When a B*tree index is created using the CREATE INDEX statement, the parameter PCTFREE can be specified.  PCTFREE specifies the percentage of space to leave  free for future updates and insertions to an index block. A value of 0 reserves no space for future inserts and updates. It allows the entire data area of the 
block to be filled when the index is created. If PCTFREE is not specified, it defaults to 10. This value reserves 10% of each block for updates to existing key values, and inserts of new key values.


当一个btree 索引使用create index语句创建的时候,可以设置pctfree 参数。pctfree指定index block中为了未来更新或者新增索引数据时预留的存储空间百分比。如果设置为0,表示不会预留存储空间。它允许index block中的data area 所有的空间都填满 索引条目 。如果pctfree 没有被指定,那么它的默认值为10。它意味着每个index block中都会预留10%的存储空间,为未来的插入和更新预留。

Thus PCTFREE is only relevant at initial index creation. It causes optimal splitting of the B*-tree in preparation for subsequent growth. The idea is to do as much splitting as  possible during initial creation and avoid having to pay the penalty later during insertion into the table. This is what a high PCTFREE setting on an index gives you. However, if your inserted keys are monotonically increasing (say a date/time field) a PCTFREE=0 is best. Only the rightmost index leaf block will be inserted into, so there's no point leaving room in the other leaf blocks at creation time.

所以pctfree只与创建索引的时候相关。它会影响b tree索引 分裂(因为后面的数据增长)的效率。比较好的办法是让索引分裂发生在索引创建阶段(预留空间多,自然需要更多的索引分裂操作)如果数据不会有增长pctfree设置为0是最好的。但是如果未来会插入大量数据,那么设置pctfree为非零那么就会有效的减少索引分裂的情况。

Following index creation, an index block can accommodate keys up to the full available data area including space for ITLs. Thus an index block will not require splitting until the available data area is fully used. The bottom line is PCTFREE is not looked at once you pass the index creation phase.  One thing to remember is that each row in the index has only one correct block it can live in, based on the key value.

索引创建,可以填满它的data area (包括itl)。所以在索引块被填满之前,是不需要分裂的。每一行在 索引中,只在一个block上,基于它的 index key column value 。

Inserting an index entry after index creation

索引创建后插入一条 索引条目

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After index creation, a new table row will create a new index entry. This entry is inserted into the appropriate index leaf block based on the index key values until the leaf block is full.  If on insert the index leaf block is full, then an index block split will occur putting half of the index entries into each new index leaf block. Within an index data block, space is reserved for the index block header. The rest of the block is available for index keys.

在索引创建之后,一个新的行插入到表中,会创建一个新的索引条目。这个条目插入到相关的索引叶块中。如果插入的索引叶块已经满了,那么索引块会发生分裂,将其一半的索引条目 放到新的索引叶块中。在索引数据块内部,空间只为索引块头保留。剩下的block space 都可以用来插入索引数据。

Where an index key value has multiple entries, these entries are made into the leaf block in ROWID order.  So all the blocks for the index key value are scanned in turn until an entry is found with a greater ROWID than the new row, and the row is inserted into that block (which may cause extra block splitting). 

当一个索引键值拥有多个条目,这些条目内部按照rowid排列。所以拥有相同index key value的block都会被按照顺序被扫描,直到找到了一个大于新行的rowid。


The reason for this is for queries such as: 
  
    SELECT some_columns FROM table WHERE COL_A  = valueA and COL_B = valueB;


If COL_A and COL_B both have non-unique indexes, then because the entries in each index are stored in ROWID order, it makes it easy to find the ROWID's that occur in both indexes.  Otherwise we would have to sort the ROWID's before we could find the ROWID's that occur in both indexes. 

如上所示,如果谓词中的列都含有非唯一索引。那么因为每个索引条目中的数据都是按照rowid的顺序排列的。所以可以很简单进行查找操作。换句话说我们必须在使用之前先按照rowid 排序。(这个情况是基于索引数据插入的说明的)

Updating an index entry

更新索引条目

~~~~~~~~~~~~~~~~~~~~~~~
There is really no concept of an UPDATE to an index. When a table row is updated, the old index key is deleted and a new key inserted (at the correct location in the B*tree).

更新就是在leaf block中删除老的index entry 并且在新的leaf block中插入新的index entry 。

Deleting an index entry

删除索引条目

~~~~~~~~~~~~~~~~~~~~~~~
When a index entry is to be deleted, the row is deleted from the index leaf block and the space within the index leaf block released to the block for further inserts with the appropriate key range.  If a leaf block has even one entry it is still part of the tree, hence only entries that belong in it positionally can be accommodated. Once a leaf block is completely empty it is put on the free list, at which point it can be used to service an index block split.

当一个索引条目被删除,相关的数据会从索引叶块中删除,并且在 索引叶块中的空间 会被释放。如果一个叶块中拥有哪怕一条记录,那么它依然属于这个btree 索引。一旦一个叶块完全空白,那么它会被放置于free list中。

Index Fragmentation
~~~~~~~~~~~~~~~~~~~
To ascertain index fragmentation, the following SQL statement can be used:


    ANALYZE INDEX &&index_name VALIDATE STRUCTURE;


    col name         heading 'Index Name'          format a30
    col del_lf_rows  heading 'Deleted|Leaf Rows'   format 99999999
    col lf_rows_used heading 'Used|Leaf Rows'      format 99999999
    col ibadness     heading '% Deleted|Leaf Rows' format 999.99999


    SELECT name,
       del_lf_rows,
       lf_rows - del_lf_rows lf_rows_used,
       to_char(del_lf_rows / (lf_rows)*100,'999.99999') ibadness
    FROM index_stats
       where name = upper('&&index_name');


    undefine index_name


As a rule of thumb if 10-15% of the table data changes, then you should consider rebuilding the index. 

索引碎片。10-15% 就需要考虑重建索引了。


B*Tree Balancing

B*Tree 平衡操作

~~~~~~~~~~~~~~~~
Oracle indexes are implemented as B* Trees which are always balanced.


oracle index btree 通常是平衡的。

In an Oracle B*tree the root of the tree is at level 0. In a very small B*tree the root block can also be a Leaf block.

在oracle btree索引中,tree 的根节点 在level 0 。在一个非常小的b tree索引中,root block 也可能是leaf block 。

In most cases, blocks on levels 0 through N-2 (where N is the height of the tree) are Branch blocks. Branch blocks do not contain data, they simply contain separators which are used to navigate from the root block to the the Leaf blocks.

大多数情况下,level 在 n-2 (n=heigh)之上的都是 branch block (分支块)。分支块不包含rowid。它们只包含分隔符。

All Leaf blocks are at level N-1 in Oracle B*trees. All data stored in a B*tree is stored in the Leaf blocks.

所有的叶块都在 level N-1 。所有的数据存储在leaf block (叶块)中。

The definition of a 'Balanced Tree' is that all the data is on the same level. Which means that the path to all data in the tree is the same length. Since all the data is stored in Leaf blocks and all the Leaf blocks are on the same level the B*trees are always balanced. There is no way to unbalance a B* tree.

“平衡树”的定义为,所有的数据在相同的level。它意味着 所有数据的访问路径,都是大致相同的。因为所有的数据存储在leaf block 并且所有的leaf block 在相同的level 上面,并且btree 通常是平衡的。没有办法去unbalance 一个 btree 。


Deletion of many rows in the B*Tree

删除B*Tree索引中大多数的行后…


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a table has 100,000 rows and 99,999 of 100,000 rows and index entries are  deleted.  How is the index balanced?

如果表中拥有100000行数据,并且其中99999行数据和索引条目被删除了,那么索引时怎样平衡的呢 ?

In this case the rows are deleted from the index, and the empty blocks inserted onto the index free list. These blocks are still part of the index and will need to be accessed when traversing the index. 

在这里数据会从index中删除,并且空的blocks 会被插入到索引 free list中。这些blocks一直是这个索引的一部分,并且并且在检索数据的时候,依然需要访问这些块。


Thus if an index has one entry in it, there is only one block (a root/leaf block) in the tree. When searching the tree only one block needs to be accessed to answer thequery. If you load a B* Tree with 100,000 rows and get a tree with say 3 Levels. 

因此,如果一个索引只有一个条目,只有一个block,root branch block=leaf block 。访问的时候仅需要访问这个块即可。如果加载了数据,with 100000条,并且得到了tree with 3 levels 。

Levels zero and one are Branch blocks used to access the data in the Leaf blocks on level 2. When querying this tree you first access the root block using the search key to find correct Branch block in level one of the Tree. 

level 0 和 1 是分支块branch block 用来取得leaf block中的数据(on level 2)。当查询这个tree , 第一需要访问的是root 块,去查找 相应的 branch block (level 1) 。

Next you use the search key and the Branch block to find the correct Leaf block that should contain the key being sought. So it takes three block accesses to answer the same query. Now if 99,999 rows were deleted from the tree the rows are removed from the index but the index is not collapsed. 

下一步,需要使用 需要查找的键值 和branch block 分支块去查找正确的 叶块。 所以需要访问3个块去相应查询。现在如果99999行数据被删除,但是索引没有收缩、合并。

In this case you still have a 3 level index to store the one row and it will still take three block accesses to access that row. The fact that we need three block accesses instead of one does not mean this tree is unbalanced. The tree is still balanced it just contains a lot of empty blocks.

在这种情况下,仍然需要3个级别的索引 去存储 这行数据,并且依然需要访问3个块才能够得到相应的数据。这意味着我们需要访问3个block 而不是一个block ,但是这并不意味着tree 不是平衡的tree 依然是平衡的 ,只是包含了一些 空块。


The reason for doing this is that, when data is inserted into a Leaf block, and there is no room for the insert, a very expensive operation called a split occurs. The split creates a new Leaf block and possibly new Branch blocks as well to maintain the balance of the tree. The split operation is by far the most expensive operation that is done in the maintenance of B* trees so we go to great lengths to avoid them. By not collapsing the now unused levels out of the B*Trees after large deletes these levels (with splits) do not have to be recreated during future inserts.

索引分裂操作是为了维护btree索引的 平衡(这是一个非常耗费资源的操作)。如果不收缩未使用的levels block ,那么这些块中的空闲空间,则会在索引条目插入的时候用到,有效的减少了索引分裂操作。

If most of the rows in a table are going to be deleted, and not refilled soon after, it is advisable to drop the index, delete the rows, and then recreate the index.

如果表中大多数的行被删除,并且在短期内不会被重新加载。那么这个时候可以考虑重建索引,drop 掉 index ,然后recreate it 。

By dropping the index you save the index maintenance that needs to be done during the delete thus speeding up the delete. By recreating the index after  the delete you create an index of optimal height and with the Leaf blocks filled to an optimal level.

删除索引,可以让我们省去索引维护的事情,比如说删除索引中数据的操作会导致性能降低。在删除数据后重建索引,可以调整height ,将leaf block 放在一个合适的级别上。

你可能感兴趣的:(Oracle)