彻底搞懂反转索引

<pre name="code" class="sql">Reverse Key Indexes

Creating a reverse key index, compared to a standard index, reverses the bytes of each column indexed (except the rowid) while keeping the column order. 

Such an arrangement can help avoid performance degradation with Real Application Clusters where modifications to the index are concentrated on a small set of 

leaf blocks. 

By reversing the keys of the index, the insertions become distributed across all leaf keys in the index.

Using the reverse key arrangement eliminates the ability to run an index range scanning query on the index. 

Because lexically adjacent keys are not stored next to each other in a reverse-key index, only fetch-by-key or full-index (table) scans can be performed.


反转索引:

创建一个反转索引,相比标准索引,在保持列顺序的情况下,翻转索引列值的字节(除了rowid).

如此的安排可以帮助避免性能灾难在RAC环境下,在RAC环境下对索引的修改讲影响一部分叶子块


通过反转索引的键值,插入编程了分布索引的所有叶子块。

使用反转索引就消除了索引Index range scan的能力


因为相邻的键值不是相邻存储在反转索引中,只用通过key来访问或者full-index (table) scan才可以


比如:seqquence 990 991 992 993 994 995 996 997 998 这样递增的都插入在索引的最右边

反转后变成 099 199 299 399 499 599 699 799 899  插入的位置为


099       199        299            399         499           599       699         799     899

本来连续的数据被打散插入到不同的leaf块中


#########################################################################################################
 在交易系统中,一些数据表基于序列的索引是热块争用的重灾区,如交易序号等,我们知道这种由序列产生的列一般是单调递增,其上的这些索引信息就会集中分布在同一个索引

叶块,形成不对称的索引,因此导致该索引块容易产生争用。


 ORACLE针对基于序列的索引的热块争用其实有一套非常可行的优化措施—反转键索引(reverse key index),这是一种十分著名的索引,其在存储键值的时候,先将键值进行翻

转。比如'1234'存储在索引中的键值是'4321'


本来要插入的索引值‘101’,‘102’,‘103’全部集中在A叶块,现在通过反转变成了‘101’,‘201’,‘301’后,就分别存储在了A,B,C三个索引叶块中,从而避免了热

块集中在A点上,如果是在RAC环境中就可以避免由于对索引的修改集中在一小部分叶块上的性能下降 ,通过使索引的键值逆序排列,可以使插入操作分布在索引的全部叶块中,从

而打散热点,避免热块争用。

  1. 创建测试表
    Create Table TEST_R(Id int,name char(10));
  
    2. 创建索引
    Create Index TEST_INDEX_R On TEST_R(Id) Reverse;
  
 begin
    for i in 1 .. 10000
    loop
    insert into TEST_R values(i,i||'a');
    end loop;
    end;
/

SQL> col object_name format a30

SQL> select object_id ,object_name from dba_objects where object_name='TEST_INDEX_R';

 OBJECT_ID OBJECT_NAME
---------- ------------------------------
     75148 TEST_INDEX_R


开始dump 索引:

将索引dump到trace文件中

alter session set events 'immediate trace name treedump level 75148';


----- begin tree dump
branch: 0x1c42afb 29633275 (0: nrow: 30, level: 1)
   leaf: 0x1c42afd 29633277 (-1: nrow: 533 rrow: 533)
   leaf: 0x1020695 16909973 (0: nrow: 293 rrow: 293)
   leaf: 0x10206b1 16910001 (1: nrow: 285 rrow: 285)
   leaf: 0x102068f 16909967 (2: nrow: 532 rrow: 532)
   leaf: 0x1020696 16909974 (3: nrow: 293 rrow: 293)
   leaf: 0x10206b2 16910002 (4: nrow: 285 rrow: 285)
   leaf: 0x1c42afc 29633276 (5: nrow: 274 rrow: 274)
   leaf: 0x10206b5 16910005 (6: nrow: 265 rrow: 265)
   leaf: 0x1020692 16909970 (7: nrow: 298 rrow: 298)
   leaf: 0x10206ac 16909996 (8: nrow: 289 rrow: 289)
   leaf: 0x1020688 16909960 (9: nrow: 305 rrow: 305)
   leaf: 0x10206aa 16909994 (10: nrow: 297 rrow: 297)
   leaf: 0x1020691 16909969 (11: nrow: 294 rrow: 294)
   leaf: 0x10206ab 16909995 (12: nrow: 303 rrow: 303)
   leaf: 0x1c42afe 29633278 (13: nrow: 323 rrow: 323)
   leaf: 0x10206a8 16909992 (14: nrow: 313 rrow: 313)
   leaf: 0x102068b 16909963 (15: nrow: 338 rrow: 338)
   leaf: 0x10206ad 16909997 (16: nrow: 351 rrow: 351)
   leaf: 0x102068d 16909965 (17: nrow: 344 rrow: 344)
   leaf: 0x1020697 16909975 (18: nrow: 361 rrow: 361)
   leaf: 0x1020689 16909961 (19: nrow: 341 rrow: 341)
   leaf: 0x1020693 16909971 (20: nrow: 356 rrow: 356)
   leaf: 0x1c42aff 29633279 (21: nrow: 323 rrow: 323)
   leaf: 0x10206a9 16909993 (22: nrow: 313 rrow: 313)
   leaf: 0x102068c 16909964 (23: nrow: 338 rrow: 338)
   leaf: 0x10206af 16909999 (24: nrow: 351 rrow: 351)
   leaf: 0x102068e 16909966 (25: nrow: 344 rrow: 344)
   leaf: 0x1020694 16909972 (26: nrow: 361 rrow: 361)
   leaf: 0x102068a 16909962 (27: nrow: 341 rrow: 341)
   leaf: 0x10206ae 16909998 (28: nrow: 356 rrow: 356)
----- end tree dump


branch: 0x1c42afb 29633275 (0: nrow: 30, level: 1) 转换为10进行是0x1c42afb = 29633275

branch 表示的是 branch block ,它后面跟了一个十六进制表示的DBA(data block address),以及用10进制表示的DBA 

DBA 之后表示在同一层次的相对位置(root 从0开始,branch 以及leaf从 -1开始)  
nrow  表示块中包含了多少条目(包括delete的条目)  
rrow  表示块中包含的实际条目(不包括delete的条目)  
level 表示从该block到leaf的深度(leaf没有 level)

这个 branch block 的 level 为1,也就是说 从这个branch block 到 leaf block 的深度为1,验证下?

搜集下统计信息,再查看
SQL> select index_name, PREFIX_LENGTH, BLEVEL, LEAF_BLOCKS
  from user_indexes
 where index_name = 'TEST_INDEX_R';  2    3  

INDEX_NAME		       PREFIX_LENGTH	 BLEVEL LEAF_BLOCKS
------------------------------ ------------- ---------- -----------
TEST_INDEX_R					      1 	 30

可以看到BLEVEL=1和dump信息一致,索引高度为2

现在我来验证一下 
branch: 0x1c42afb 29633275 (0: nrow: 30, level: 1) 是不是 root block , 我查询这个 branch 的 DBA

SQL> select dbms_utility.data_block_address_file('29633275') FILE_ID,
       dbms_utility.data_block_address_block('29633275') BLOCK_ID
  from dual;   2    3  

   FILE_ID   BLOCK_ID
---------- ----------
	 7     273147


Btree 索引的 root block总是segment header+1,所以我查询该索引的段头  
  
SQL> select  OWNER,segment_name,header_file,header_block from dba_segments where segment_name='TEST_INDEX_R';

OWNER			       SEGMENT_NAME									 HEADER_FILE HEADER_BLOCK
------------------------------ --------------------------------------------------------------------------------- ----------- ------------
SCOTT			       TEST_INDEX_R										   7	   273146


Oracle 中提供了dbms_utility来求的这个地址对应的文件号和块号(传入的参数是十进制的那个值).

查看这个leaf block对应的文件号和块号
leaf: 0x1c42afd 29633277 (-1: nrow: 533 rrow: 533)


SQL> select dbms_utility.data_block_address_file(29633277) fno,
       dbms_utility.data_block_address_block(29633277) bkno
  from dual  2    3  ;

       FNO	 BKNO
---------- ----------
	 7     273149


SQL> select file_id,block_id,blocks from dba_extents where segment_name='TEST_INDEX_R';

   FILE_ID   BLOCK_ID	  BLOCKS
---------- ---------- ----------
	 7     273144	       8

BLOCK_ID	NUMBER	 	Starting block number of the extent

BLOCKS	NUMBER	 	Size of the extent in Oracle blocks


将索引数据dump出来,dump 7号文件的273149块,alter system dump datafile 7 block 273149;
row#0[4506] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 02 c2
col 1; len 6; (6):  01 c4 2a f4 00 64
row#1[4519] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 03 c2
col 1; len 6; (6):  01 c4 2a f4 00 c8
row#2[4532] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 04 c2
col 1; len 6; (6):  01 c4 2a f4 01 2c
row#3[4545] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 05 c2
col 1; len 6; (6):  01 c4 2a f5 00 20
row#4[4558] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 06 c2
col 1; len 6; (6):  01 c4 2a f5 00 84
row#5[4571] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 07 c2
col 1; len 6; (6):  01 c4 2a f5 00 e8
row#6[4584] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 08 c2

看前三列为例:
row#0[4506] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 02 c2
col 1; len 6; (6):  01 c4 2a f4 00 64
row#1[4519] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 03 c2
col 1; len 6; (6):  01 c4 2a f4 00 c8
row#2[4532] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 04 c2
col 1; len 6; (6):  01 c4 2a f4 01 2c

row#3[4545] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 05 c2
col 1; len 6; (6):  01 c4 2a f5 00 20

row#4[4558] flag: ------, lock: 0, len=13
col 0; len 3; (3):  02 06 c2
col 1; len 6; (6):  01 c4 2a f5 00 84

row#0行号.

col 0第一列(本例中第一列为是id), len 3表示长度是3, (3)表示占了两个字节,02 02 c2是id的值(这里值是1的16进制表示)的存储表示.

由于是反转索引存储的应该是c20202


SQL> set serveroutput on
SQL>  declare
   n number;
 begin
   dbms_stats.convert_raw_value('c20202',
                                n);
   dbms_output.put_line(n);
 end;  2    3    4    5    6    7  
  8  /
101


第2列是201


第3列是301

第4列是401


第5列是501


验证了反转索引插入数据会插入到所有的叶子块中

比如你插入的是991 那么会以119的形式存储,但是查询不受影响 仍旧查991

posted @ 2014-05-22 21:14  czcb  阅读(1403)  评论(0编辑  收藏  举报