B-Tree索引详解及联合索引使用

B-Tree索引原理详解部分转载自:http://zsuil.com/?p=1184

一.B-tree索引详解

B-tree索引(或Balanced Tree),是一种很普遍的数据库索引结构,oracle默认的索引类型(本文也主要依据oracle来讲)。其特点是定位高效、利用率高、自我平衡,特别适用于高基数字段,定位单条或小范围数据非常高效。理论上,使用B-tree在亿条数据与100条数据中定位记录的花销相同。

数据结构利用率高、定位高效

B-tree索引的数据结构如下:


 

结构看起来B-tree索引与Binary Tree相似,但在细节上有所不同,上图中用不同颜色的标示出了B-tree索引的几个主要特点:

  • 树形结构:由根节(root)、分支(branches)、叶(leaves)三级节点组成,其中分支节点可以有多层。
  • 多分支结构:与binary tree不相同的是,b-tree索引中单root/branch可以有多个子节点(超过2个)。
  • 双向链表:整个叶子节点部分是一个双向链表(后面会描述这个设计的作用)
  • 单个数据块中包括多条索引记录

这里先把几个特点罗列出来,后面会说到各自的作用。

结构上B-tree与Binary Tree的区别,在于binary中每节点代表一个数值,而balanced中root和B-tree节点中记录了多条”值范围”条目(如:[60-70][70-80]),这些”值范围”条目分别指向在其范围内的叶子节点。既root与branch可以有多个分支,而不一定是两个,对数据块的利用率更高

在Leaf节点中,同样也是存放了多条索引记录,这些记录就是具体的索引列值,和与其对应的rowid。另外,在叶节点层上,所有的节点在组成了一个双向链表。
了解基本结构后,下图展示定位数值82的过程:



演算如下:
读取root节点,判断82大于在0-120之间,走左边分支。
读取左边branch节点,判断82大于80且小于等于120,走右边分支。
读取右边leaf节点,在该节点中找到数据82及对应的rowid
使用rowid去物理表中读取记录数据块(如果是count或者只select rowid,则最后一次读取不需要)

在整个索引定位过程中,数据块的读取只有3次。既三次I/O后定位到rowid。

而由于B-tree索引对结构的利用率很高,定位高效。当1千万条数据时,B-tree索引也是三层结构(依稀记得亿级数据才是3层与4层的分水岭)。定位记录仍只需要三次I/O,这便是开头所说的,100条数据和1千万条数据的定位,在b-tree索引中的花销是一样的。


平衡扩张

除了利用率高、定位高效外,B-tree的另一个特点是能够永远保持平衡,这与它的扩张方式有关。(unbalanced和hotspot是两类问题,之前我一直混在一起),先描述下B-tree索引的扩张方式:

新建一个索引,索引上只会有一个leaf节点,取名为Node A,不断的向这个leaf节点中插入数据后,直到这个节点满,这个过程如下图(绿色表示新建/空闲状态,红色表示节点没有空余空间):


当Node A满之后,我们再向表中插入一条记录,此时索引就需要做拆分处理:会新分配两个数据块NodeB & C,如果新插入的值,大于当前最大值,则将Node A中的值全部插入Node B中,将新插入的值放到Node C中;否则按照5-5比例,将已有数据分别插入到NodeB与C中。

无论采用哪种分割方式,之前的leaf节点A,将变成一个root节点,保存两个范围条目,指向B与C,结构如下图(按第一种拆分形式):


当Node C满之后,此时 Node A仍有空余空间存放条目,所以不需要再拆分,而只是新分配一个数据块Node D,将在Node A中创建指定到Node D的条目:


如果当根节点Node A也满了,则需要进一步拆分:新建Node E&F&G,将Node A中范围条目拆分到E&F两个节点中,并建立E&F到BCD节点的关联,向Node G插入索引值。此时E&F为branch节点,G为leaf节点,A为Root节点:

 

 

在整个扩张过程中,B-tree自身总能保持平衡,Leaf节点的深度能一直保持一致


 

实际应用中的一些问题

前面说完了B-tree索引的结构与扩张逻辑,接下来讲一些B-tree索引在应用中的一些问题:

单一方向扩展引起的索引竞争(Index Contention)

若索引列使用sequence或者timestamp这类只增不减的数据类型。这种情况下B-tree索引的增长方向总是不变的,不断的向右边扩展,因为新插入的值永远是最大的。

当一个最大值插入到leaf block中后,leaf block要向上传播,通知上层节点更新所对应的“值范围”条目中的最大值,因此所有靠右边的block(从leaf 到branch甚至root)都需要做更新操作,并且可能因为块写满后执行块拆分。

如果并发插入多个最大值,则最右边索引数据块的的更新与拆分都会存在争抢,影响效率。在AWR报告中可以通过检测enq: TX – index contention事件的时间来评估争抢的影响。解决此类问题可以使用Reverse Index解决,不过会带来新的问题。

Index Browning 索引枯萎(不知道该怎么翻译这个名词,就是指leaves节点”死”了,树枯萎了)

其实oracle针对这个问题有优化机制,但优化的不彻底,所以还是要拿出来的说。

我们知道当表中的数据删除后,索引上对应的索引值是不会删除的,特别是在一性次删除大批量数据后,会造成大量的dead leaf挂到索引树上。考虑以下示例,如果表100以上的数据会部被删除了,但这些记录仍在索引中存在,此时若对该列取max():


通过与之前相同演算,找到了索引树上最大的数据块,按照记录最大的值应该在这里,但发现这数据块里的数据已经被清空了,与是利用B-tree索引的另一个特点:leaves节点是一个双向列表,若数据没有找到就去临近的一个数据块中看看,在这个数据块中发现了最大值99。

在计算最大值的过程中,这次的定位多加载了一个数据块,再极端的情况下,大批量的数据被删除,就会造成大量访问这些dead leaves。

针对这个问题的一般解决办法是重建索引,但记住! 重建索引并不是最优方案,详细原因可以看看这。使用coalesce语句来整理这些dead leaves到freelist中,就可以避免这些问题。理论上oracle中这步操作是可以自动完成的,但在实际中一次性大量删除数据后,oracle在短时间内是反应不过来的。

更多资料:

Ask Tom: Possible arcane question on B*Tree indexes as Oracle sees them 有点老了
So When Does An Oracle B-Tree Index Increase In Height
http://docs.oracle.com/cd/E11882_01/server.112/e25789/indexiot.htm#autoId6
http://www.dba-oracle.com/t_index_leaf_block_contention_tuning.htm


二.联合索引使用规则

    我在此文中测试总结了一部分联合索引的使用规则,可能并没有包含所有的规则,毕竟是以一道题来展开的分析。另外如果有这样一种情况:where c1='1' and c2>'0' and c3<'5' and c4='1';


结果显示2个字段用到了索引,肯定就是c1,c2字段了。那么为什么c3以后会断掉呢?

那么我来大胆的分析一下:

        此索引是B-Tree类型的联合索引,如果是按照索引字段顺序应该是c1,c2...c4顺序查询到的结果应该是从一个分支走到一个数据块得到结果。但是如果c2>'0'使用了范围查找,那么B-Tree树c1下c2这一级可能会检索到多个树的分支,导致c2下c3以及后面的树延伸分支会更多,多个分支最终对应多个数据块

        是否是这多个分支对应多个数据块引起的c2后面的字段无法再使用索引了呢?此时是由于c2范围查找引起的c3以后的字段无法使用联合索引。那么我们在来看一种缺少c2的情况呢: where c1='1' and c4>'0' and c3='1';如下图:


where中没有c2的情况下,只有c1字段使用了该索引,这种情况我们是否可以理解为:B-Tree树c1下c2的所有分支呢,所有分支其实类似于范围查询的多个分支。

        以上结论,只是个人分析,因为我确实没有查到联合索引为什么会“断裂”的原因,“断裂”一词也没有找到更为专业的形容词。如果某位大神明确这个原理,请留言指正并附上依据,为我解惑,先行谢过。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章