Calvin's Tech Space

成于坚忍,毁于浮躁

   :: 首页 :: 联系 :: 聚合  :: 管理

 

两种使用B树在列上建立索引的情况:

索引用于访问表中的行:通过读索引来访问表中的行。此时你希望访问表中很少的一部分行(只占一个很小的百分比)。

索引用于回答一个查询:索引包含了足够的信息来回答整个查询,我根本不用去访问表。在这种情况下,索引则用作一个“较瘦“版本的表,即通过查询索引就能找到查询结果,在这种情况下,可以通过处理标准100%的数据,而不像第一种情况中只能访问少量的数据。


为什么在通过索引访问表时如果数据量比较大的话,使用索引反而会降低性能?

一般来讲,B*树索引会放在频繁使用查询谓词的列上,而且我们希望从表中只返回少量的数据(只占很小的百分比),或者最终用户请求立即得到反馈。在一个瘦(thin)表(也就是说,只有很少的几个列,或者列很小)上,这个百分比可能相当小。使用这个索引的查询应该只获取表中2%3%(或者更少)的行。在一个胖(fat)表中(也就是说,这个表有很多列,或者列很宽),百分比则可能会上升到表的20%25%。以上建议不一定直接适用于每一个人;这个比例并不直观,但很精确。

索引按索引键的顺序存储。索引会按键的有序顺序进行访问。索引指向的块则随机地存储在堆中。因此,我们通过索引访问表时,会执行大量分散、随机的I/O。这里“分散“(scattered)是指,索引会告诉我们读取块1,然后是块1000、块205、块321、块1、块1032、块1,等等,它不会要求我们按一种连续的方式读取块1、然后是块2,接着是块3(原因在与表中的每一行并没有按照索引键的顺序存储在堆中,否则不会出现这种情况)。我们将以一种非常随意的方式读取和重新读取块。这种块I/O可能非常慢。

下面来看这样一个简化的例子,假设我们通过索引读取一个瘦表,而且要读取表中20%的行。若这个表中有100,000行,其中的20%就是2,0000行。如果行大小约为80字节,在一个块大小为8KB的数据库中,每个块上则有大约100行。这说明,这个表有大约1000个块。了解了这些情况,计算起来就非常容易了。我们要通过索引读取20,000行;这说明,大约是20,000TABLE  ACCESS  BY  ROWID操作。为此要处理20,000个表块来执行这个查询。不过,整个表才有大约1000个块!因而最后会平均把表中的每一个块读取和处理20(效果就是缓存的命中率很低,缓存的相邻块的数据没有用到)。 即使把行的大小提高一个数量级,达到每行800字节,这样每块有11.行,现在表中就有11.,000个块。要通过索引访问20,000行,仍要求我们把每一个块平均读取2次。在这种情况下,全表扫描就比使用索引高效得多,因为每个块只会命中一次。如果查询使用这个索引来访问数据,效率都不会高,除非对于800字节的行,平均只访问表中不到5%的数据,因为这样一来,就只会访问大约5,000个块,如果是80字节的行,则访问的数据应当只占更小的百分比,大约0.5%或更少(这就是为什么在一次查询中一个“胖表”的数据获取百分比比一个“瘦表”相对高的原因)。

因此,根本原因是因为缓存命中率降低,出现了大量随机分散的I/O操作。


物理组织对索引效率的影响

此外,数据在磁盘上如何物理组织,对上述过程也会有显著影响。

表会很自然地按主键顺序聚簇(因为数据或多或少就是已这种属性增加的)。当然,它不一定严格按照键聚簇(要想做到这一点,必须使用一个IOT),但是,一般来讲,主键值彼此接近的行的物理位置也会“靠“在一起。如果发出以下查询:

select * from T where primary_key between :x and :y

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


聚簇因子

接下来,我们来看Oracle所用的一些信息。我们要特别查看USER_INDEXES视图中的CLUSTERING_FACTOR列。Oracle reference手册指出了这个列有以下含义:

根据索引的值指示表中行的有序程度:

 如果这个值与块数接近,则说明表相当有序,得到了很好的组织,在这种情况下,同一个叶子块中的索引条目可能指向同一个数据块上的行。

 如果这个值与行数接近,表的次序可能就是非常随机的。在这种情况下,同一个叶子块上的索引条目不太可能指向同一个数据块上的行。

可以把聚簇因子(clusteringfactor)看作是通过索引读取整个表时对表执行的逻辑I/O次数。也就是说,CLUSTERING_FACTOR指示了表相对于索引本身的有序程度,查看这些索引时,会看到以下结果:

ops$tkyte@ORA10G> select a.index_name,           

2 b.num_rows,

3 b.blocks,

4 a.clustering_factor

5 from user_indexes a, user_tables b

6 where  index_name in ('COLOCATED_PK', 'DISORGANIZED_PK' )

7 and a.table_name = b.table_name

8 /

INDEX_NAME  NUM_ROWS  BLOCKS  CLUSTERING_FACTOR

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

COLOCATED_PK  100000  1252 1190

DISORGANIZED_PK  100000  1219 99932

所以数据库说:“如果通过索引COLOCATED_PK从头到尾地读取COLOCATED表中的每一行,就要执行1190I/O。不过,如果我们对DISORGANIZED表做同样的事情,则会对这个表执行99,932I/O“。之所以存在这么大的区别,原因在于,Oracle对索引结构执行区间扫描时,如果它发现索引中的下一行几乎总与前一行在同一个数据库块上,就不会再执行另一个I/O从缓冲区缓存中获得表块。它已经有表块的一个句柄,只需直接使用就可以了。不过,如果下一行不在同一个块上,就会释放当前的这个块,而执行另一个I/O从缓冲区缓存获取要处理的下一个块。因此,在我们对索引执行区间扫描时,COLOCATED_PK索引会发现下一行几乎总于前一行在同一个块上。DISORGANIZED_PK索引发现的情况则恰好相反。

以上讨论的关键点是,索引并不一定总是合适的访问方法。优化器也许选择不使用索引,而且如前面的例子所示,这种选择可能很正确。影响优化器是否使用索引的因素有很多,包括物理数据布局。因此,你可能会矫枉过正,力图重建所有的表来使所有索引有一个好的聚簇因子,但是在大多数情况下这可能只会浪费时间。只有当你在对表中的大量数据(所占百分比很大)执行索引区间扫描时,这才会产生影响。另外必须记住,对于一个表来说,一般只有一个索引能有合适的聚簇因子!表中的行可能只以一种方式排序。在前面所示的例子中,如果Y列上还有一个索引,这个索引在COLOCATED表中可能就不能很好地聚簇,而在DISORGANIZED表中则恰好相反。如果你认为数据物理聚簇很重要,可以考虑使用一个IOTB*树聚簇,或者在连续地重建表时考虑散列聚簇。


B*树索引小结

什么时候建立索引,在哪些列上建立索引,你的设计中必须注意这些问题。索引并不一定就意味着更快的访问;实际上你会发现,在许多情况下,如果Oracle使用索引,反而会使性能下降。这实际上两个因素的一个函数,其中一个因素是通过索引需要访问表中多少数据(占多大的百分比),另一个因素是数据如何布局。如果能完全使用索引“回答问题“(而不用表),那么访问大量的行(占很大的百分比)就是有意义的,因为这样可以避免读表所带来的额外的分散I/O。如果使用索引来访问表,可能就要确保只处理整个表中的很少一部分(只占很小的百分比)。

应该在应用的设计期间考虑索引的设计和实现,而不要事后才想起来(我就经常见到这种情况)。如果对如何访问数据做了精心的计划和考虑,大多数情况下就能清楚地知道需要什么索引。

posted on 2009-09-20 16:46 calvin 阅读(2623) 评论(0)  编辑  收藏 所属分类: Oracle

只有注册用户登录后才能发表评论。


网站导航: