Oracle 基础篇 --- 重建B树索引对性能的影响
2015-07-15 16:56
549 查看
#####重建B树索引
######1. 如何重建B树索引
重建索引有两种方法:
一种是最简单的,删除原索引,然后重建;
第二种是使用ALTER INDEX … REBUILD;
命令对索引进行重建。第二种方式是从oracle 7.3.3版本开始引入的,从而使得用户在重建索引时不必删除原索引再重新CREATE INDEX了。ALTER INDEX … REBUILD相对CREATE INDEX有以下好处:
1) 它使用原索引的叶子节点作为新索引的数据来源。我们知道,原索引的叶子节点的数据块通常都要比表里的数据块要少很多,因此进行的I/O就会减少;同时,由于原索引的叶子节点里的索引条目已经排序了,因此在重建索引的过程中,所做的排序工作也要少的多。
2) 自从oracle 8.1.6以来,ALTER INDEX … REBUILD命令可以添加ONLINE短语。这使得在重建索引的过程中,用户可以继续对原来的索引进行修改,也就是说可以继续对表进行DML操作。
而同时,ALTER INDEX … REBUILD与CREATE INDEX也有很多相同之处:
1) 它们都可以通过添加PARALLEL提示进行并行处理。
2) 它们都可以通过添加NOLOGGING短语,使得重建索引的过程中产生最少的重做条目(redo entry)。
3) 自从oracle 8.1.5以来,它们都可以田间COMPUTE STATISTICS短语,从而在重建索引的过程中,就生成CBO所需要的统计信息,这样就避免了索引创建完毕以后再次运行analyze或dbms_stats来收集统计信息。
当我们重建索引以后,在物理上所能获得的好处就是能够减少索引所占的空间大小(特别是能够减少叶子节点的数量)。而索引大小减小以后,又能带来以下若干好处:
1) CBO对于索引的使用可能会产生一个较小的成本值,从而在执行计划中选择使用索引。
2) 使用索引扫描的查询扫描的物理索引块会减少,从而提高效率。
3) 由于需要缓存的索引块减少了,从而让出了内存以供其他组件使用。
尽管重建索引具有一定的好处,但是盲目的认为重建索引能够解决很多问题也是不正确的。比如我见过一个生产系统,每隔一个月就要重建所有的索引(而且我相信,很多生产系统可能都会这么做),其中包括一些100GB的大表。为了完成重建所有的索引,往往需要把这些工作分散到多个晚上进行。事实上,这是一个7×24的系统,仅重建索引一项任务就消耗了非常多的系统资源。但是每隔一段时间就重建索引有意义吗?这里就有一些关于重建索引的很流行的说法,主要包括:
1) 如果索引的层级超过X(X通常是3)级以后需要通过重建索引来降低其级别。
2) 如果经常删除索引键值,则需要定时重建索引来收回这些被删除的空间。
3) 如果索引的clustering_factor很高,则需要重建索引来降低该值。
4) 定期重建索引能够提高性能。
对于第一点来说,我们在前面已经知道,B树索引是一棵在高度上平衡的树,所以重建索引基本不可能降低其级别,除非是极特殊的情况导致该索引有非常大量的碎片,导致B树索引“虚高”,那么这实际又来到第二点上(因为碎片通常都是由于删除引起的)。实际上,对于第一和第二点,我们应该通过运行
######2. 重建B树索引对于clustering_factor的影响
而对于
在上图四中,我们有一个表,该表有4个数据块,以及20条记录。在列N1上有一个索引,上图中的每个小黑点就表示一个索引条目。列N1的值如图所示。而N1的索引的叶子节点包含的值为:A、B、C、D、E、F。如果oracle开始扫描索引的底部,叶子节点包含的第一个N1值为A,那么根据该值可以知道对应的ROWID位于第一个数据块的第三行里,所以我们的计数器增加1。同时,A值还对应第二个数据块的第四行,由于跳转到了不同的数据块上,所以计数器再加1。同样的,在处理B时,可以知道对应第一个数据块的第二行,由于我们从第二个数据块跳转到了第一个数据块,所以计数器再加1。同时,B值还对应了第一个数据块的第五行,由于我们这里没有发生跳转,所以计数器不用加1。
在上面的图里,在表的每一行的下面都放了一个数字,它用来显示计数器跳转到该行时对应的值。当我们处理完索引的最后一个值时,我们在数据块上一共跳转了十次,所以该索引的clustering_factor为10。
注意第二个数据块,clustering_factor为8出现了4次。因为在索引里N1为E所对应的4个索引条目都指向了同一个数据块。从而使得clustering_factor不再增长。同样的现象出现在第三个数据块中,它包含三条记录,它们的值都是C,对应的clustering_factor都是6。
从clustering_factor的计算方法上可以看出,我们可以知道它的最小值就等于表所含有的数据块的数量;而最大值就是表所含有的记录的总行数。很明显,clustering_factor越小越好,越小说明通过索引查找表里的数据行时需要访问的表的数据块越少。
重建索引对于减小
首先我们创建一个测试表:
所以我们可以得出结论,如果仅仅是为了降低索引的
######2. 重建B树索引对于查询性能的影响
重建索引对于性能的提高到底会有什么作用。假设我们有一个表,该表具有1百万条记录,占用了100000个数据块。而在该表上存在一个索引,在重建之前的pct_used为50%,高度为3,分支节点块数为40个,再加一个根节点块,叶子节点数为10000个;重建该索引以后,pct_used为90%,高度为3,分支节点块数下降到20个,再加一个根节点块,而叶子节点数下降到5000个。那么从理论上说:
如果通过索引获取单独1条记录来说:
重建之前的成本:1个根+1个分支+1个叶子+1个表块=4个逻辑读
重建之后的成本:1个根+1个分支+1个叶子+1个表块=4个逻辑读
性能提高百分比:0
如果通过索引获取100条记录(占总记录数的0.01%)来说,分两种情况:
最差的clustering_factor(即该值等于表的数据行数):
重建之前的成本:1个根+1个分支+0.000110000(1个叶子)+100个表块=103个逻辑读
重建之后的成本:1个根+1个分支+0.00015000(1个叶子)+100个表块=102.5个逻辑读
性能提高百分比:0.5%(也就是减少了0.5个逻辑读)
最好clustering_factor(即该值等于表的数据块):
重建之前的成本:1个根+1个分支+0.000110000(1个叶子)+0.0001100000(10个表块)=13个逻辑读
重建之后的成本:1个根+1个分支+0.00015000(1个叶子)+0.0001100000(10个表块)=12.5个逻辑读
性能提高百分比:3.8%(也就是减少了0.5个逻辑读)
如果通过索引获取10000条记录(占总记录数的1%)来说,分两种情况:
最差的clustering_factor(即该值等于表的数据行数):
重建之前的成本:1个根+1个分支+0.0110000(100个叶子)+10000个表块=10102个逻辑读
重建之后的成本:1个根+1个分支+0.015000(50个叶子)+10000个表块=10052个逻辑读
性能提高百分比:0.5%(也就是减少了50个逻辑读)
最好clustering_factor(即该值等于表的数据块):
重建之前的成本:1个根+1个分支+0.0110000(100个叶子)+0.01100000(1000个表块)=1102个逻辑读
重建之后的成本:1个根+1个分支+0.015000(50个叶子)+0.01100000(1000个表块)=1052个逻辑读
性能提高百分比:4.5%(也就是减少了50个逻辑读)
如果通过索引获取100000条记录(占总记录数的10%)来说,分两种情况:
最差的clustering_factor(即该值等于表的数据行数):
重建之前的成本:1个根+1个分支+0.110000(1000个叶子)+100000个表块=101002个逻辑读
重建之后的成本:1个根+1个分支+0.15000(500个叶子)+100000个表块=100502个逻辑读
性能提高百分比:0.5%(也就是减少了500个逻辑读)
最好clustering_factor(即该值等于表的数据块):
重建之前的成本:1个根+1个分支+0.110000(1000个叶子)+0.1100000(10000个表块)=11002个逻辑读
重建之后的成本:1个根+1个分支+0.15000(500个叶子)+0.1100000(10000个表块)=10502个逻辑读
性能提高百分比:4.5%(也就是减少了500个逻辑读)
如果通过索引获取100000条记录(占总记录数的10%)来说,分两种情况:
对于快速全索引扫描来说,假设每次获取8个数据块:
重建之前的成本:(1个根+40个分支+10000个叶子)/ 8=1256个逻辑读
重建之后的成本:(1个根+40个分支+5000个叶子)/ 8=631个逻辑读
性能提高百分比:49.8%(也就是减少了625个逻辑读)
从上面有关性能提高的理论描述可以看出,对于通过索引获取的记录行数不大的情况下,索引碎片对于性能的影响非常小;当通过索引获取较大的记录行数时,索引碎片的增加可能导致对于索引逻辑读的增加,但是索引读与表读的比例保持不变;同时,我们从中可以看到,clustering_factor对于索引读取的性能有很大的影响,并且对于索引碎片所带来的影响具有很大的作用;最后,看起来,索引碎片似乎对于快速全索引扫描具有最大的影响。
我们来看两个实际的例子,分别是clustering_factor为最好和最差的两个例子。测试环境为8KB的数据块,表空间采用ASSM的管理方式。先做一个最好的clustering_factor的例子,创建测试表并填充1百万条数据。
显示了不同的索引下,运行测试语句所需的时间对比情况
上面是对最好的clustering_factor所做的测试,那么对于最差的clustering_factor会怎么样呢?我们将rebuild_test中的id值反过来排列,也就是说,比如对于id为3478的记录,将id改为8743。这样的话,就将把原来按顺序排列的id值彻底打乱,从而使得id上的索引的clustering_factor变成最差的。为此,我写了一个函数用来反转id的值。
######1. 如何重建B树索引
重建索引有两种方法:
一种是最简单的,删除原索引,然后重建;
第二种是使用ALTER INDEX … REBUILD;
命令对索引进行重建。第二种方式是从oracle 7.3.3版本开始引入的,从而使得用户在重建索引时不必删除原索引再重新CREATE INDEX了。ALTER INDEX … REBUILD相对CREATE INDEX有以下好处:
1) 它使用原索引的叶子节点作为新索引的数据来源。我们知道,原索引的叶子节点的数据块通常都要比表里的数据块要少很多,因此进行的I/O就会减少;同时,由于原索引的叶子节点里的索引条目已经排序了,因此在重建索引的过程中,所做的排序工作也要少的多。
2) 自从oracle 8.1.6以来,ALTER INDEX … REBUILD命令可以添加ONLINE短语。这使得在重建索引的过程中,用户可以继续对原来的索引进行修改,也就是说可以继续对表进行DML操作。
而同时,ALTER INDEX … REBUILD与CREATE INDEX也有很多相同之处:
1) 它们都可以通过添加PARALLEL提示进行并行处理。
2) 它们都可以通过添加NOLOGGING短语,使得重建索引的过程中产生最少的重做条目(redo entry)。
3) 自从oracle 8.1.5以来,它们都可以田间COMPUTE STATISTICS短语,从而在重建索引的过程中,就生成CBO所需要的统计信息,这样就避免了索引创建完毕以后再次运行analyze或dbms_stats来收集统计信息。
当我们重建索引以后,在物理上所能获得的好处就是能够减少索引所占的空间大小(特别是能够减少叶子节点的数量)。而索引大小减小以后,又能带来以下若干好处:
1) CBO对于索引的使用可能会产生一个较小的成本值,从而在执行计划中选择使用索引。
2) 使用索引扫描的查询扫描的物理索引块会减少,从而提高效率。
3) 由于需要缓存的索引块减少了,从而让出了内存以供其他组件使用。
尽管重建索引具有一定的好处,但是盲目的认为重建索引能够解决很多问题也是不正确的。比如我见过一个生产系统,每隔一个月就要重建所有的索引(而且我相信,很多生产系统可能都会这么做),其中包括一些100GB的大表。为了完成重建所有的索引,往往需要把这些工作分散到多个晚上进行。事实上,这是一个7×24的系统,仅重建索引一项任务就消耗了非常多的系统资源。但是每隔一段时间就重建索引有意义吗?这里就有一些关于重建索引的很流行的说法,主要包括:
1) 如果索引的层级超过X(X通常是3)级以后需要通过重建索引来降低其级别。
2) 如果经常删除索引键值,则需要定时重建索引来收回这些被删除的空间。
3) 如果索引的clustering_factor很高,则需要重建索引来降低该值。
4) 定期重建索引能够提高性能。
对于第一点来说,我们在前面已经知道,B树索引是一棵在高度上平衡的树,所以重建索引基本不可能降低其级别,除非是极特殊的情况导致该索引有非常大量的碎片,导致B树索引“虚高”,那么这实际又来到第二点上(因为碎片通常都是由于删除引起的)。实际上,对于第一和第二点,我们应该通过运行
ALTER INDEX … REBUILD命令以后检查
index_stats.pct_used字段来判断是否有必要重建索引。
SQL> analyze index emp3_name_ix validate structure; Index analyzed. SQL> select LF_ROWS, LF_ROWS_LEN,DEL_LF_ROWS,DEL_LF_ROWS_LEN, USED_SPACE, BTREE_SPACE from index_stats; LF_ROWS LF_ROWS_LEN DEL_LF_ROWS DEL_LF_ROWS_LEN USED_SPACE BTREE_SPACE ---------- ----------- ----------- --------------- ---------- ----------- 110002 2053033 10000 150000 2065045 6324964 SQL> alter index emp3_name_ix rebuild; Index altered. SQL> analyze index emp3_name_ix validate structure; Index analyzed. SQL> select LF_ROWS, LF_ROWS_LEN,DEL_LF_ROWS,DEL_LF_ROWS_LEN, USED_SPACE, BTREE_SPACE from index_stats; LF_ROWS LF_ROWS_LEN DEL_LF_ROWS DEL_LF_ROWS_LEN USED_SPACE BTREE_SPACE ---------- ----------- ----------- --------------- ---------- ----------- 100002 1903033 0 0 1906995 2134964
######2. 重建B树索引对于clustering_factor的影响
而对于
clustering_factor来说,它是用来比较索引的顺序程度与表的杂乱排序程度的一个度量。Oracle在计算某个
clustering_factor时,会对每个索引键值查找对应到表的数据,在查找的过程中,会跟踪从一个表的数据块跳转到另外一个数据块的次数(当然,它不可能真的这么做,源代码里只是简单的扫描索引,从而获得
ROWID,然后从这些
ROWID获得表的数据块的地址)。每一次跳转时,有个计数器就会增加,最终该计数器的值就是
clustering_factor。下图四描述了这个原理。
在上图四中,我们有一个表,该表有4个数据块,以及20条记录。在列N1上有一个索引,上图中的每个小黑点就表示一个索引条目。列N1的值如图所示。而N1的索引的叶子节点包含的值为:A、B、C、D、E、F。如果oracle开始扫描索引的底部,叶子节点包含的第一个N1值为A,那么根据该值可以知道对应的ROWID位于第一个数据块的第三行里,所以我们的计数器增加1。同时,A值还对应第二个数据块的第四行,由于跳转到了不同的数据块上,所以计数器再加1。同样的,在处理B时,可以知道对应第一个数据块的第二行,由于我们从第二个数据块跳转到了第一个数据块,所以计数器再加1。同时,B值还对应了第一个数据块的第五行,由于我们这里没有发生跳转,所以计数器不用加1。
在上面的图里,在表的每一行的下面都放了一个数字,它用来显示计数器跳转到该行时对应的值。当我们处理完索引的最后一个值时,我们在数据块上一共跳转了十次,所以该索引的clustering_factor为10。
注意第二个数据块,clustering_factor为8出现了4次。因为在索引里N1为E所对应的4个索引条目都指向了同一个数据块。从而使得clustering_factor不再增长。同样的现象出现在第三个数据块中,它包含三条记录,它们的值都是C,对应的clustering_factor都是6。
从clustering_factor的计算方法上可以看出,我们可以知道它的最小值就等于表所含有的数据块的数量;而最大值就是表所含有的记录的总行数。很明显,clustering_factor越小越好,越小说明通过索引查找表里的数据行时需要访问的表的数据块越少。
重建索引对于减小
clustering_factor没有用处。
首先我们创建一个测试表:
SQL> create table clustfact_test(id number, name varchar2(10)); Table created. SQL> create index idx_clustfact_test on clustfact_test(id); Index created. SQL> begin 2 for i in 1.. 100000 loop 3 insert into clustfact_test values(mod(i,200), to_char(i)); 4 end loop; 5 commit; 6 end; 7 / PL/SQL procedure successfully completed. SQL> exec dbms_stats.gather_table_stats(user,'clustfact_test',cascade=>true); PL/SQL procedure successfully completed. #因为使用了mod的关系,最终数据在表里排列的形式为: 0,1,2,3,4,5,…,197,198,199,0,1,2,3,…, 197,198,199,0,1,2,3,…, 197,198,199,0,1,2,3,… SQL> select num_rows, blocks from user_tables where table_name = 'CLUSTFACT_TEST'; NUM_ROWS BLOCKS ---------- ---------- 100000 244 SQL> select num_rows, distinct_keys, AVG_LEAF_BLOCKS_PER_KEY, AVG_DATA_BLOCKS_PER_KEY, CLUSTERING_FACTOR from user_indexes where 2 index_name = 'IDX_CLUSTFACT_TEST'; NUM_ROWS DISTINCT_KEYS AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY CLUSTERING_FACTOR ---------- ------------- ----------------------- ----------------------- ----------------- 100000 200 1 198 39683 #从上面的avg_data_blocks_per_key的值为198可以知道,每个键值平均分布在198个数据块里,而整个表也就244个数据块。这也就是说,要获取某个键值的所有记录,几乎每次都需要访问所有的数据块。从这里已经可以猜测到clustering_factor会非常大。事实上,该值近4万,也说明该索引并不会很有效。 SQL> select * from clustfact_test where id = 100; 500 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 1230726760 ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 500 | 4500 | 69 (2)| 00:00:01 | |* 1 | TABLE ACCESS FULL| CLUSTFACT_TEST | 500 | 4500 | 69 (2)| 00:00:01 | ------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("ID"=100) Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 281 consistent gets 244 physical reads 0 redo size 12114 bytes sent via SQL*Net to client 887 bytes received via SQL*Net from client 35 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 500 rows processed #很明显,CBO弃用了索引,而使用了全表扫描。这实际上已经说明由于索引的clustering_factor过高,导致通过索引获取数据时跳转的数据块过多,成本过高,因此直接使用全表扫描的成本会更低。 #这时我们来重建索引看看会对clustering_factor产生什么影响。从下面的测试中可以看到,没有任何影响。 SQL> alter index idx_clustfact_test rebuild; Index altered. SQL> select num_rows, distinct_keys, AVG_LEAF_BLOCKS_PER_KEY, AVG_DATA_BLOCKS_PER_KEY, CLUSTERING_FACTOR from user_indexes where 2 index_name = 'IDX_CLUSTFACT_TEST'; NUM_ROWS DISTINCT_KEYS AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY CLUSTERING_FACTOR ---------- ------------- ----------------------- ----------------------- ----------------- 100000 200 1 198 39683 #那么当我们将表里的数据按照id的顺序(也就是索引的排列顺序)重建时,该SQL语句会如何执行? SQL> create table clustfact_test_temp as select * from clustfact_test order by id; Table created. SQL> truncate table clustfact_test; Table truncated. SQL> insert into clustfact_test select * from clustfact_test_temp; 100000 rows created. SQL> exec dbms_stats.gather_table_stats(user,'clustfact_test',cascade=>true); PL/SQL procedure successfully completed. SQL> select num_rows, distinct_keys, avg_leaf_blocks_per_key, avg_data_blocks_per_key, 2 clustering_factor from user_indexes where index_name = 'IDX_CLUSTFACT_TEST'; NUM_ROWS DISTINCT_KEYS AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY CLUSTERING_FACTOR ---------- ------------- ----------------------- ----------------------- ----------------- 100000 200 1 1 244 SQL> set autotrace traceonly; SQL> select * from clustfact_test where id = 100; 500 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 60478562 -------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 451 | 4059 | 3 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| CLUSTFACT_TEST | 451 | 4059 | 3 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | IDX_CLUSTFACT_TEST | 451 | | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("ID"=100) Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 74 consistent gets 0 physical reads 0 redo size 13715 bytes sent via SQL*Net to client 887 bytes received via SQL*Net from client 35 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 500 rows processed
所以我们可以得出结论,如果仅仅是为了降低索引的
clustering_factor而重建索引没有任何意义。降低
clustering_factor的关键在于重建表里的数据。只有将表里的数据按照索引列排序以后,才能切实有效的降低
clustering_factor。但是如果某个表存在多个索引的时候,需要仔细决定应该选择哪一个索引列来重建表。
######2. 重建B树索引对于查询性能的影响
重建索引对于性能的提高到底会有什么作用。假设我们有一个表,该表具有1百万条记录,占用了100000个数据块。而在该表上存在一个索引,在重建之前的pct_used为50%,高度为3,分支节点块数为40个,再加一个根节点块,叶子节点数为10000个;重建该索引以后,pct_used为90%,高度为3,分支节点块数下降到20个,再加一个根节点块,而叶子节点数下降到5000个。那么从理论上说:
如果通过索引获取单独1条记录来说:
重建之前的成本:1个根+1个分支+1个叶子+1个表块=4个逻辑读
重建之后的成本:1个根+1个分支+1个叶子+1个表块=4个逻辑读
性能提高百分比:0
如果通过索引获取100条记录(占总记录数的0.01%)来说,分两种情况:
最差的clustering_factor(即该值等于表的数据行数):
重建之前的成本:1个根+1个分支+0.000110000(1个叶子)+100个表块=103个逻辑读
重建之后的成本:1个根+1个分支+0.00015000(1个叶子)+100个表块=102.5个逻辑读
性能提高百分比:0.5%(也就是减少了0.5个逻辑读)
最好clustering_factor(即该值等于表的数据块):
重建之前的成本:1个根+1个分支+0.000110000(1个叶子)+0.0001100000(10个表块)=13个逻辑读
重建之后的成本:1个根+1个分支+0.00015000(1个叶子)+0.0001100000(10个表块)=12.5个逻辑读
性能提高百分比:3.8%(也就是减少了0.5个逻辑读)
如果通过索引获取10000条记录(占总记录数的1%)来说,分两种情况:
最差的clustering_factor(即该值等于表的数据行数):
重建之前的成本:1个根+1个分支+0.0110000(100个叶子)+10000个表块=10102个逻辑读
重建之后的成本:1个根+1个分支+0.015000(50个叶子)+10000个表块=10052个逻辑读
性能提高百分比:0.5%(也就是减少了50个逻辑读)
最好clustering_factor(即该值等于表的数据块):
重建之前的成本:1个根+1个分支+0.0110000(100个叶子)+0.01100000(1000个表块)=1102个逻辑读
重建之后的成本:1个根+1个分支+0.015000(50个叶子)+0.01100000(1000个表块)=1052个逻辑读
性能提高百分比:4.5%(也就是减少了50个逻辑读)
如果通过索引获取100000条记录(占总记录数的10%)来说,分两种情况:
最差的clustering_factor(即该值等于表的数据行数):
重建之前的成本:1个根+1个分支+0.110000(1000个叶子)+100000个表块=101002个逻辑读
重建之后的成本:1个根+1个分支+0.15000(500个叶子)+100000个表块=100502个逻辑读
性能提高百分比:0.5%(也就是减少了500个逻辑读)
最好clustering_factor(即该值等于表的数据块):
重建之前的成本:1个根+1个分支+0.110000(1000个叶子)+0.1100000(10000个表块)=11002个逻辑读
重建之后的成本:1个根+1个分支+0.15000(500个叶子)+0.1100000(10000个表块)=10502个逻辑读
性能提高百分比:4.5%(也就是减少了500个逻辑读)
如果通过索引获取100000条记录(占总记录数的10%)来说,分两种情况:
对于快速全索引扫描来说,假设每次获取8个数据块:
重建之前的成本:(1个根+40个分支+10000个叶子)/ 8=1256个逻辑读
重建之后的成本:(1个根+40个分支+5000个叶子)/ 8=631个逻辑读
性能提高百分比:49.8%(也就是减少了625个逻辑读)
从上面有关性能提高的理论描述可以看出,对于通过索引获取的记录行数不大的情况下,索引碎片对于性能的影响非常小;当通过索引获取较大的记录行数时,索引碎片的增加可能导致对于索引逻辑读的增加,但是索引读与表读的比例保持不变;同时,我们从中可以看到,clustering_factor对于索引读取的性能有很大的影响,并且对于索引碎片所带来的影响具有很大的作用;最后,看起来,索引碎片似乎对于快速全索引扫描具有最大的影响。
我们来看两个实际的例子,分别是clustering_factor为最好和最差的两个例子。测试环境为8KB的数据块,表空间采用ASSM的管理方式。先做一个最好的clustering_factor的例子,创建测试表并填充1百万条数据。
create table rebuild_test(id number, name varchar2(10)); begin for i in 1..1000000 loop insert into rebuild_test values(i, to_char(i)); if mod(i, 10000)=0 then commit; end if; end loop; end; / #创建一个pctfree为50%的索引,来模拟索引碎片,分析并记录索引信息。 SQL> create index idx_rebuild_test on rebuild_test(id) pctfree 50; Index created. SQL> exec dbms_stats.gather_table_stats(user,'rebuild_test',cascade=>true); PL/SQL procedure successfully completed. #该表具有1百万条记录,分布在2328个数据块中。同时由于我们的数据都是按照顺序递增插入的,所以可以知道,在id列上创建的索引都是具有最好的clustering_factor值的。我们运行以下查询测试语句,分别返回1、100、1000、10000、50000、100000以及1000000条记录。 #然后运行测试语句,记录每条查询语句所需的时间; select * from rebuild_test where id = 10; select * from rebuild_test where id between 100 and 199; select * from rebuild_test where id between 1000 and 1999; select * from rebuild_test where id between 10000 and 19999; select /*+ index(rebuild_test) */ * from rebuild_test where id between 50000 and 99999; select /*+ index(rebuild_test) */ * from rebuild_test where id between 100000 and 199999; select /*+ index(rebuild_test) */ * from rebuild_test where id between 1 and 1000000; select /*+ index_ffs(rebuild_test) */ id from rebuild_test where id between 1 and 1000000; #注index_ffs: hint instructs the optimizer to perform a fast full index scan rather than a full table scan. #接下来以pctfree为10%重建索引,来模拟修复索引碎片,分析并记录索引信息。 SQL> alter index idx_rebuild_test rebuild pctfree 10; Index altered. SQL> exec dbms_stats.gather_table_stats(user,'rebuild_test',cascade=>true); PL/SQL procedure successfully completed. SQL> analyze index idx_rebuild_test validate structure; Index analyzed. #两个索引信息的对比情况 select ui.pct_free, ids.height, ids.BLOCKS, ids.br_blks, ids.lf_blks, ids.pct_used, ui.clustering_factor from index_stats ids, user_indexes ui where INDEX_NAME = 'IDX_REBUILD_TEST'; PCT_FREE HEIGHT BLOCKS BR_BLKS LF_BLKS PCT_USED CLUSTERING_FACTOR ---------- ---------- ---------- ---------- ---------- ---------- ----------------- 50 3 4224 8 4096 49 2326 PCT_FREE HEIGHT BLOCKS BR_BLKS LF_BLKS PCT_USED CLUSTERING_FACTOR ---------- ---------- ---------- ---------- ---------- ---------- ----------------- 10 3 2304 5 2226 90 2326
显示了不同的索引下,运行测试语句所需的时间对比情况
记录数 | 占记录总数的百分比 | pctused(50%) | pctused(90%) | 性能提高百分比 |
---|---|---|---|---|
1条记录 | 0.0001% | 0.01 | 0.01 | 0.00% |
100条记录 | 0.0100% | 0.01 | 0.01 | 0.00% |
1000条记录 | 0.1000% | 0.01 | 0.01 | 0.00% |
10000条记录 | 1.0000% | 0.02 | 0.02 | 0.00% |
50000条记录 | 5.0000% | 0.06 | 0.06 | 0.00% |
100000条记录 | 10.0000% | 1.01 | 1.00 | 0.99% |
1000000条记录 | 100.0000% | 13.05 | 11.01 | 15.63%=(13.05-11.01)/13.05 |
1000000条记录(FFS) | 100.0000% | 7.05 | 7.02 | 0.43% |
CREATE OR REPLACE FUNCTION get_reverse_value (id IN NUMBER) RETURN VARCHAR2 IS ls_id VARCHAR2 (10); ls_last_item VARCHAR2 (10); ls_curr_item VARCHAR2 (10); ls_zero VARCHAR2 (10); li_len INTEGER; lb_stop BOOLEAN; BEGIN ls_id := TO_CHAR (id); li_len := LENGTH (ls_id); ls_last_item := ''; ls_zero := ''; lb_stop := FALSE; WHILE li_len > 0 LOOP ls_curr_item := SUBSTR (ls_id, li_len, 1); IF ls_curr_item = '0' AND lb_stop = FALSE THEN ls_zero := ls_zero || ls_curr_item; ELSE lb_stop := TRUE; ls_last_item := ls_last_item || ls_curr_item; END IF; ls_id := SUBSTR (ls_id, 1, li_len - 1); li_len := LENGTH (ls_id); END LOOP; RETURN (ls_last_item || ls_zero); END get_reverse_value; /* 此程序如下:例如 123 循环1: ls_curr_item=3, ls_last_item= '||3=3, ls_id=12, li_en=2 循环2:ls_curr_item=2, ls_last_item=3||2=32, ls_id=1, li_en=1 循环3:ls_curr_item=1, ls_last_item=32||1=321, ls_id='', li_en=0 此时 li_en=0 跳出循环,返回 ls_last_item=321 例如100 循环1: ls_curr_item=0, ls_zero= ''||0=0, ls_id=20, li_en=2 循环2:ls_curr_item=0, ls_zero=0||0=00, ls_id=1, li_en=1 循环3:ls_curr_item=1, ls_last_item=''||1=1, ls_id='', li_en=0 此时 li_en=0 跳出循环,返回 ls_last_item=1,ls_zero=00,ls_last_item || ls_zero=100 */ SQL> create table rebuild_test_cf as select * from rebuild_test; Table created. SQL> update rebuild_test_cf set id=get_reverse_value(id); 1000000 rows updated. SQL> select * from rebuild_test_cf where name < 20; ID NAME ---------- ---------- 21 12 31 13 41 14 51 15 61 16 71 17 81 18 91 19 SQL> create index idx_rebuild_test_cf on rebuild_test_cf(id); Index created. select ui.pct_free, ids.height, ids.BLOCKS, ids.br_blks, ids.lf_blks, ids.pct_used, ui.clustering_factor from index_stats ids, user_indexes ui where INDEX_NAME = 'IDX_REBUILD_TEST_CF'; PCT_FREE HEIGHT BLOCKS BR_BLKS LF_BLKS PCT_USED CLUSTERING_FACTOR ---------- ---------- ---------- ---------- ---------- ---------- ----------------- 10 3 2304 5 2226 90 998914 SQL> select /*+ index(rebuild_test_cf) */ * from rebuild_test_cf where id between 1 and 1000000; 1000000 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 509582566 ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000K| 11M| 1001K (1)|03:20:18| | 1 | TABLE ACCESS BY INDEX ROWID| REBUILD_TEST_CF | 1000K| 11M| 1001K (1)|03:20:18| |* 2 | INDEX RANGE SCAN | IDX_REBUILD_TEST_CF | 1000K| | 2238 (1)|00:00:27| ------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("ID">=1 AND "ID"<=1000000) Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 1067727 consistent gets 1938 physical reads 0 redo size 29269206 bytes sent via SQL*Net to client 733850 bytes received via SQL*Net from client 66668 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1000000 rows processed SQL> select /*+ index_ffs(rebuild_test_cf) */ id from rebuild_test_cf where id between 1 and 1000000; 1000000 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 3567300343 -------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000K| 4882K| 616 (2)| 00:00:08 | |* 1 | INDEX FAST FULL SCAN| IDX_REBUILD_TEST_CF | 1000K| 4882K| 616 (2)| 00:00:08 | -------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("ID">=1 AND "ID"<=1000000) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 68759 consistent gets 2190 physical reads 0 redo size 18380300 bytes sent via SQL*Net to client 733850 bytes received via SQL*Net from client 66668 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1000000 rows processed
相关文章推荐
- 基于 Red Hat 的发行版 Oracle Linux 正式发布Oracle Linux 7.1
- Oracle Containers for J2EE远程安全漏洞(CVE-2014-0413)
- Oracle 10g R2不能使用EM的问题
- 表空间操作
- PreparedStatement中in子句的处理
- MySQL 优化
- VMware下RedHat4.8_64位安装Oracle 10g RAC--简略脚本
- oracle sql日期比较
- 基于 Red Hat 的发行版 Oracle Linux 正式发布Oracle Linux 7.1
- OS block size和Oracle block size,查找OS Blocksize的方法
- oracle中创建数据库和表空间的几点总结
- 数据库自动备份脚本
- Google排名优化的几个影响因素
- 二级域名原理以及程序
- DB2优化(简易版)
- Mysql limit 优化,百万至千万级快速分页 复合索引的引用并应用于轻量级框架
- C#中尾递归的使用、优化及编译器优化
- PostgreSQL教程(八):索引详解
- 优化Ruby脚本效率实例分享