oracle 索引块结构及索引访问路径

最近研究了下oracle的B-tree索引,有了些发现。放在这里,与大家共享。

这篇文章主要介绍oracle B-tree索引的internal结构,顺便说说几种索引扫描方式对于索引块的扫描路径。

首先放上官方文档,对索引结构的图形描述。图片有点大,我不知道如何在显示的时候缩小,将就看吧。

oracle 索引块结构及索引访问路径_第1张图片

上面是一个deep为3的索引结构图。deep的值就是从索引的root block到索引的leaf block所要经历的block数。

说道索引的internal结构,我们还是先dump出索引的块来看看。

首先,要找到索引的块在哪里。举个例子,使用如下的语句:

SQL> select a.owner,a.index_name,a.blevel from sys.dba_indexes a where a.index_name='IDX_TEST_OBJECTS2_INDEX1';


OWNER        INDEX_NAME   BLEVEL
------------------------------ ------------------------------ ----------
SCOTT        IDX_TEST_OBJECTS2_INDEX1        1


select * from dba_extents a where a.owner='SCOTT' and a.segment_name='IDX_TEST_OBJECTS2_INDEX1' order by a.EXTENT_ID;

我这里的例子查出来如下结果:我是截取的,举例子一条就够了。注意下,我这里拿出了extent_id为0的记录。这是这个索引段的第一个extent。包含了大量的信息。asm的管理结构包含在其中。

OWNER SCOTT
SEGMENT_NAME IDX_TEST_OBJECTS2_INDEX1
PARTITION_NAME
SEGMENT_TYPE INDEX
TABLESPACE_NAME USERS
EXTENT_ID 0
FILE_ID 4
BLOCK_ID 14400
BYTES 65536
BLOCKS 8
RELATIVE_FNO 4

然后根据结果,来进行dump。

SQL> oradebug setmypid;
Statement processed.
SQL> oradebug tracefile_name;
/home/oracle/app/oracle/diag/rdbms/uorcl/iorcl/trace/iorcl_ora_70023.trc
SQL> alter system dump datafile &file block min &min block max &max;
Enter value for file: 4
Enter value for min: 14400
Enter value for max: 14407
old   1: alter system dump datafile &file block min &min block max &max
new   1: alter system dump datafile 4 block min 14400 block max 14407
System altered.
SQL> 

拿到dump文件后,可以打开看看。

这里要插一下,其实可以先用treedump将整个索引结构框架dump出来,这样可以有个整体印象。如下:

SQL> select owner,object_name,object_id from sys.dba_objects a where a.object_name='IDX_TEST_OBJECTS2_INDEX1';


OWNER
------------------------------
OBJECT_NAME
--------------------------------------------------------------------------------
 OBJECT_ID
----------
SCOTT
IDX_TEST_OBJECTS2_INDEX1
     90549

SQL> oradebug setmypid;
Statement processed.
SQL> oradebug tracefile_name;
/home/oracle/app/oracle/diag/rdbms/uorcl/iorcl/trace/iorcl_ora_70702.trc

SQL> alter session set events 'immediate trace name treedump level 90549';


Session altered.

截取部分结果如下。这里可以看到,我这里只有一个branch块,剩下的都是leaf block。为什么呢,因为我这个索引的数据比较少,他的blevel是1.高度仅仅是2而已。

 38 branch: 0x1003843 16791619 (0: nrow: 263, level: 1)
 39    leaf: 0x1003844 16791620 (-1: nrow: 256 rrow: 256)
 40    leaf: 0x1003845 16791621 (0: nrow: 256 rrow: 256)
 41    leaf: 0x1003846 16791622 (1: nrow: 256 rrow: 256)
 42    leaf: 0x1003847 16791623 (2: nrow: 256 rrow: 256)
 43    leaf: 0x1003848 16791624 (3: nrow: 256 rrow: 256)

然后再回来看看刚才的dump结构。多的不说,主要说下关键的几个地方。

同样是截取了部分。

[oracle@localhost ~]$ vi /home/oracle/app/oracle/diag/rdbms/uorcl/iorcl/trace/iorcl_ora_69063.trc


  627 Block header dump:  0x01003843   --0x01003843是当前数据块地址
 628  Object id on Block? Y
 629  seg/obj: 0x161b5  csc: 0x00.3c768c  itc: 1  flg: E  typ: 2 - INDEX
 630      brn: 0  bdba: 0x1003840 ver: 0x01 opc: 0
 631      inc: 0  exflg: 0
 632 
 633  Itl           Xid                  Uba         Flag  Lck        Scn/Fsc       --这是初始化事务槽部分
 634 0x01   0xffff.000.00000000  0x00000000.0000.00  C---    0  scn 0x0000.003c768c
 635 Branch block dump
 636 =================
 637 header address 139967411788364=0x7f4cb3dc1a4c
 638 kdxcolev 1      --这个地方,index  block的level。这里是1,因为这个索引深度为2,所以这个block就是root了。
 639 KDXCOLEV Flags = - - -
 640 kdxcolok 0
 641 kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
 642 kdxconco 3
 643 kdxcosdc 0
 644 kdxconro 262
 645 kdxcofbo 552=0x228
 646 kdxcofeo 3916=0xf4c
 647 kdxcoavs 3364
 648 kdxbrlmc 16791620=0x1003844      --这个地方要注意一下,16791620=0x1003844一个是10进制,一个是16进制。这是一个数据块的地址。指向的leaf/branch block所包含的index  key的最大值小于或者等于这个root块所连接的其它所有的leaf/branch block。实际上就是这个root块其下层block的首块。
 649 kdxbrsno 0
 650 kdxbrbksz 8056
 651 kdxbr2urrc 0
 652 row#0[8034] dba: 16791621=0x1003845    --这个是包含下面这个col 0的index  key的block地址。这里有个点,标位”注1”,放在最后说。
 653 col 0; len 11; (11):  41 50 45 58 5f 30 33 30 32 30 30    --这是复合索引首列的index  key。这里标位”注2”,后面说
 654 col 1; len 4; (4):  c3 09 2d 63  -这是复合索引第二列的index  key  这里标注“注3”
 655 col 2; TERM
 656 row#1[8012] dba: 16791622=0x1003846
 657 col 0; len 11; (11):  41 50 45 58 5f 30 33 30 32 30 30
 658 col 1; len 4; (4):  c3 09 30 37
 659 col 2; TERM  --这里没有显示出来值,有些branch  block会显示部分rowid的信息


上面说了root  或者branch  block。其实leaf  block有两个不同的地方需要介绍下。

5556  Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
5557 0x01   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000
5558 0x02   0xffff.000.00000000  0x00000000.0000.00  C---    0  scn 0x0000.003c768c
5559 Leaf block dump
5560 ===============
5561 header address 139968438844004=0x7f4cf113ba64
5562 kdxcolev 0
5563 KDXCOLEV Flags = - - -
5564 kdxcolok 0
5565 kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
5566 kdxconco 3
5567 kdxcosdc 0
5568 kdxconro 256
5569 kdxcofbo 548=0x224
5570 kdxcofeo 1378=0x562
5571 kdxcoavs 830
5572 kdxlespl 0
5573 kdxlende 0
5574 kdxlenxt 16791624=0x1003848    --这里就是leaf block双向链接的所在了。
5575 kdxleprv 16791622=0x1003846    --同上。
5576 kdxledsz 0
5577 kdxlebksz 8032
5578 row#0[8006] flag: ------, lock: 0, len=26
5579 col 0; len 11; (11):  41 50 45 58 5f 30 33 30 32 30 30
5580 col 1; len 4; (4):  c3 09 33 0b
5581 col 2; len 6; (6):  01 00 3c cc 00 1f    --这里是index  key对应的表行的rowid。


现在来说下几种索引访问方式:

这里先放一张注解的图。这个是打了标号的图。说起来方便。

oracle 索引块结构及索引访问路径_第2张图片

1  index  full  scan :有序的索引全扫描。扫描出来的结果是排序的。按照index  key,rowid进行排序。访问索引的路径大致为1>2>5>6>7>8;

2  index fast full scan:索引快速全扫描。扫描出来的结构是无序的。这个访问路径与上面的图无关。与table full  scan类似,是从段头的bitmap 开始的。

3  index range  scan:索引范围扫描,访问路径大致为1>2>5>6,与1类似。但是是由范围的。也可以是1>2>6或者是1>4>7>8.本质在于入口必须从root>branch>leaf,访问第一个leaf后,后面就依赖leaf间的双向链接获取下一个leaf block。

4  index  skip  scan:索引跳跃扫描,访问路径可以有多个入口。1>2>5和1>4>7.从5到7之间“skip”了6.

上面的结论也是我推断出来的,暂时没有时间验证。但是验证方法我可以给出来:

     使用10046跟踪一次索引访问过程。但是要在重启库后,在访问相关对象前进行。因为这时候,对于所有block的访问都会留下记录。


最后来解释下上面遗留的两个问题:

注1: 652 row#0[8034] dba: 16791621=0x1003845 这个可以通过如下sql获取对应块的fileid和block号:

          select sys.dbms_utility.data_block_address_file(rn)||','||sys.dbms_utility.data_block_address_block(rn) 
from (select 16791621 rn from dual);

注2: 653 col 0; len 11; (11):  41 50 45 58 5f 30 33 30 32 30 30   这个值可以用下面的方法取出其中的index key的值。但是注意,这个方法值针对字符类型有效。

create or replace procedure sys.hex2char(i_vc_input in varchar2) is
o_vc_return_flag varchar2(4000);
i_vc_input_compress varchar2(4060);
vc_characterset varchar2(4000);
type type_character_number is table of number index by binary_integer;
characters_number type_character_number;
j number;
n_temp number;
n_skipflag number;
begin
  if( instr(i_vc_input,',') > 0 ) then
      i_vc_input_compress := trim(replace(i_vc_input,',',''));
  elsif ( instr(i_vc_input, ' ') > 0 ) then
        i_vc_input_compress := trim(replace(i_vc_input,' ',''));
  else
        i_vc_input_compress := trim(i_vc_input);
  end if;
  select value into vc_characterset from nls_database_parameters where parameter='NLS_CHARACTERSET';
  j:= 1;
  n_skipflag:=0;
  for i in 1 .. length(i_vc_input_compress) loop
    if  n_skipflag > 0  then 
      n_skipflag := n_skipflag - 1; 
    end if;
    if ( n_skipflag = 0 ) then
          select to_number(substr(i_vc_input_compress,i,2),'XXXXXXXXXXXX') into n_temp from dual;
       if (n_temp < 128 ) then
         characters_number(j) := n_temp;
         j := j + 1; n_skipflag := 2;
       elsif ( vc_characterset = 'ZHS16GBK' )then
         select to_number(substr(i_vc_input_compress,i,4),'XXXXXXXXXXXX') into n_temp from dual;
         characters_number(j) := n_temp;
         j := j + 1;
         n_skipflag := 4;
       elsif( vc_characterset='AL32UTF8' ) then
            select to_number(substr(i_vc_input_compress,i,6),'XXXXXXXXXXXX') into n_temp from dual;
             characters_number(j) := n_temp; j := j + 1; n_skipflag := 6;
       else select to_number(substr(i_vc_input_compress,i,4),'XXXXXXXXXXXX') into n_temp from dual;
            characters_number(j) := n_temp;
            j := j + 1; n_skipflag := 4;
       end if;
    end if;
  end loop;
  if ( characters_number.count > 0 ) then
    for k in characters_number.first .. characters_number.last loop
         if( characters_number(k) > 31 ) then
             dbms_output.put(chr(characters_number(k)));
         end if;
    end loop;
  end if;
dbms_output.put_line(chr(10));
exception
when others then
     o_vc_return_flag :=  sqlcode || sqlerrm;
     dbms_output.put_line(o_vc_return_flag);
return;
end;

/

注3:因为这个复合索引是varchar2和number类型字段的复合索引。所以这第二个index key需要用下面的方法取出来。

create or replace function undump_number(in_datalist in varchar2) return number is


v_regexp constant varchar2(10) := '[^ ]+';
v_tmnt   constant varchar2(1)  := ' ';
v_minus  constant number  := 193;
v_power  constant number  :=100;


v_ln number;
v_num number;
o_result number := 0;
v_datalist varchar2(100);


type type_character_char is table of varchar2(5) index by binary_integer;
characters_char type_character_char;


begin
  v_datalist := rtrim(in_datalist)||v_tmnt;
  v_num := length(translate(v_datalist,v_tmnt||v_datalist,v_tmnt));
  SELECT REGEXP_SUBSTR(v_datalist, v_regexp, 1, LEVEL) bulk collect into characters_char   FROM DUAL connect by level <=v_num;
  v_ln := to_number(characters_char(1),'xx')-v_minus;
  for i in 2..v_num loop
    o_result := o_result+(to_number(characters_char(i),'xx')-1)*power(v_power,v_ln-i+2);
  end loop;
  return o_result;
end;

/

到这里就结束了。


你可能感兴趣的:(oracle)