B*Tree索引是最常见的索引结构,默认建立的索引就是这种类型的索引。B*Tree索引在检索高基数数据列(高基数数据列是指该列有很多不同的值)时提供了最好的性能。当取出的行数占总行数比例较小时B-Tree索引比全表检索提供了更有效的方法。但当检查的范围超过表的10%时就不能提高取回数据的性能。B-Tree索引是基于二叉树的,由分支块(branch block)和叶块(leaf block)组成。在树结构中,位于最底层底块被称为叶块,包含每个被索引列的值和行所对应的rowid。在叶节点的上面是分支块,用来导航结构,包含了索引列(关键字)范围和另一索引块的地址。
理想情况下,树的高度应该最小,因为为了获取某个信息而进行的读操作的次数与树的高度成正比。为了定位某个关键字,最少需要进行三个读操作(两个读操作发生在根/分支级,第三个读动作发生在叶节点级)。需要记住的是,根据索引所布局的方式不同,每个读操作都有可能是逻辑读或物理读。如果单独的某个读操作将所需要的块放置在内存中,那么后面的读操作将有可能直接从内存中进行;否则,它们将需要从硬盘中进行读。高度与阶数成反比,阶数越高,高度将越低(这种树称为“扁平”树)。在任何B*树的实现中,一个重要目标就是增加分支数、减少高度,从而保证对叶节点的访问被加速。
在B *树的实现过程中,主要应该注意的问题是INSERT操作、DELETE操作与UPDATE操作。
在每个INSERT操作过程中,关键字必须被插入在正确叶节点的位置。如果叶节点已满,不能容纳更多的关键字,就必须将叶节点拆分。拆分的方法有两种:
1)如果新关键字值在所有旧叶节点块的所有关键字中是最大的,那么所有的关键字将按照99:1的比例进行拆分,使得在新的叶节点块中只存放有新关键字,而其他的所有关键字(包括所有删除的关键字)仍然保存在旧叶节点块中。
2)如果新关键字值不是最大的,那么所有的关键字将按照50:50的比例进行拆分,这时每个叶节点块(旧与新)中将各包含原始叶节点中的一半关键字。
这个拆分必须通过一个指向新叶节点的新入口向上传送到父节点。如果父节点已满,那么这个父节点也必须进行拆分,并且需要将这种拆分向上传送到父节点的父节点。这时,如果这个父节点也已满,将继续进行这个过程。这样,某个拆分可能最终被一直传送到根节点。如果根节点满了,根结点也将进行分裂。根结点在进行分裂的时候,就是树的高度增加的时候。根节点进行分裂的方式跟其他的的节点分裂的方式相比较,在物理位置上的处理也是不同的。根节点分裂时,将原来的根结点分裂为分支节点或叶节点,保存到新的块中,而将新的根节点信息保存到原来的根结点块中,这样做的是为因为避免修改数据字典所带来的相对较大的开销。
在索引的每一个层次之间,每一个层最左边的节点的block头部都有一个 指向下层最左边的块的指针,这样有利于 fast full scan 的快速定位最左边的叶子节点。
每个拆分过程都是要花费一定的开销的,特别是要进行物理硬盘I/O动作。此外,在进行拆分之前,Oracle必须查找到一个空块,用来保存这个拆分。可以用以下步骤来进行查找空块的动作:
1) 在索引的自由列表(free-list, 又称为空闲列表) 中查到一个空闲块,可以通过CREATE/ALTER INDEX命令为一个索引定义多个空闲列表。索引空闲列表并不能帮助Oracle查找一个可用来存放将要被插入的新关键字的块。这是因为关键字值不能随机地存放在索引中可用的第一个“空闲”叶节点块中,这个值必须经过适当的排序之后,放置在某个特定的叶节点块中。只有在块拆分过程中才需要使用索引的空闲列表,每个空闲列表都包含有一个关于“空”块的链接列表。当为某个索引定义了多个空闲列表时,首先将从分配给进程的空间列表中扫描一个空闲块。如果没有找到所需要的空闲块,将从主空闲列表中进行扫描空闲块的动作。
2) 如果没有找到任何空闲块,Oracle将试图分配另一个扩展段。如果在表空间中没有更多的自由空间,Oracle将产生错误ORA-01654。
3) 如果通过上述步骤,找到了所需的空闲块,那么这个索引的高水位标(HWM)将加大。
4) 所找到的空闲块将用来执行拆分动作。
在创建B*树索引时,一个需要注意的问题就是要避免在运行时进行拆分,或者,要在索引创建过程中进行拆分(“预拆分”),从而使得在进行拆分时能够快速命中,以便避免运行时插入动作。当然,这些拆分也不仅仅局限于插入动作,在进行更新的过程中也有可能会发生拆分动作。
下面来分析一下在Oracle中进行已索引关键字的UPDATE动作时,会发生什么事情。
索引更新完全不同于表更新,在表更新中,数据是在数据块内部改变的(假设数据块中有足够的空间来允许进行这种改变);但在索引更新中,如果有关键字发生改变,那么它在树中的位置也需要发生改变。请记住,一个关键字在B *树中有且只有一个位置。因此,当某个关键字
发生改变时,关键字的旧表项必须被删除,并且需要在一个新的叶节点上创建一个新的关键字。旧的表项有可能永远不会被重新使用,这是因为只有在非常特殊的情况下, Oracle才会重用关键字表项槽,例如,新插入的关键字正好是被删除的那个关键字(包括数据类型、长度
等等)。(这里重用的是块,但完全插入相同的值的时候,也不一定插入在原来的被删除的位置,只是插入在原来的块中,可能是该块中的一个新位置。也正因为如此,在索引块中保存的的记录可能并不是根据关键字顺序排列的,随着update等的操作,会发生变化。)那么,这种情况发生的可能性有多大呢?许多应用程序使用一个数列来产生NUMBER关键字(特别是主关键字)。除非它们使用了RECYCLE选项,否则这个数列将不会两次产生完全相同的数。这样,索引中被删除的空间一直没有被使用。这就是在大规模删除与更新过程中,表大小不断减小或至少保持不变但索引不断加大的原因。
通过上面对B *树的分析,可以得出以下的应用准则:
1)避免对那些可能会产生很高的更新动作的列进行索引。
2)避免对那些经常会被删除的表中的多个列进行索引。若有可能,只对那些在这样的表上会进行删除的主关键字与/或列进行索引。如果对多个列进行索引是不可避免的,那么就应该考虑根据这些列对表进行划分,然后在每个这样的划分上执行TRUNCATE动作(而不是DELETE动作)。TRUNCATE在与DROP STORAGE短语一同使用时,通过重新设置高水位标来模拟删除表与索引以及重新创建表与索引的过程。
3)避免为那些唯一度不高的列创建B*树索引。这样的低选择性将会导致树节点块的稠密性,从而导致由于索引“平铺( flat)”而出现的大规模索引扫描。唯一性的程度越高,性能就越好,因为这样能够减少范围扫描,甚至可能用唯一扫描来取代范围扫描。
4)空值不应该存储在单列索引中。对于复合索引的方式,只有当某个列不空时,才需要进行值的存储。在为DML语句创建IS NULL或IS NOT NULL短语时,应该切记这个问题。
5)IS NULL不会导致索引扫描,而一个没有带任何限制的IS NOT NULL则可能会导致完全索引扫描。
2. PCTFREE的重要性
对于B*树索引, PCTFREE可以决定叶节点拆分的extent。也就是说,PCTFREE用来说明在某个块中的自由空间数目,以便于后来的更新动作。但是,对于索引(与表不同),这些更新动作没有任何意义,因为更新会删除一个索引,然后又出现插入一个新索引。
对于索引,PCTFREE大多数是在索引创建过程中发生作用,可以用一个非零值来说明块拆分比例。如果在索引创建过程中,PCTFREE被设置为20,那么有80%的叶节点将可能会包含关键字信息。但是,剩余的20%将用来作为关键字信息后来插入到叶节点块中时使用。这样将能够保证在需要进行叶节点块的拆分时,运行时的插入开销最小。虽然一个较高的PCTFREE可能会使得索引创建时间增加,但它能够防止在实际的使用过程中命中性能的降低。因此,那些正在等待某个行被插入的终端用户并不会因为需要进行叶节点块的拆分而使得自己的性能受到影响。
基于上述信息,可以得出以下结论:
1)某个索引的PCTFREE主要是在索引创建时使用。在实际的应用过程中,PCTFREE将被忽略。
2)如果表是一个经常被访问、包含有大量DML改变(通过交互式用户屏幕)的表,那么就应该为OLTP应用程序指定一个较高的PCTFREE。
3)如果索引创建时间很关键,那么就应该指定一个较低PCTFREE。这样在每个叶节点块中将会压缩有多个行,从而可以避免在索引创建时进行更多的拆分。这一点对于2 4×7客户站点非常重要,因为在大多数情况下索引创建过程需要很多的系统停工时间(特别是在表有几百万行时更为如此)。
4)对于任何其值不断增加的列,最好是设置一个非常低的PCTFREE(甚至可以为0)。这是因为只有那些最右方的叶节点块总是会被插入,从而使得树向右增长。而左边的叶节点将一直为静止状态,因此没有必要使得这些块的任何部分为空(通过使用非零PCTFREE)。
索引的实验:
SQL> create table test(id number,name varchar2(100));
表已创建
SQL> create index idx_test on test(id);
索引已创建
SQL> select file_id,extent_id,block_id from dba_extents where segment_name=’IDX_TEST’ ;
FILE_ID EXTENT_ID BLOCK_ID
———- ———- ———-
6 0 41
此时索引为空,我们来转储一下41号块的信息
SQL> alter system dump datafile 6 block 41;
系统已更改。
可以通过以下的方法得到转储的数据文件
在Oracle Database 11g之前,要想获得跟踪文件的名称,通常我们需要执行一系列的查询,常用的脚本如下:
SQL> SELECT a.VALUE || b.symbol || c.instance_name || ‘_ora_’ || d.spid || ‘.trc‘
2 trace_file
3 FROM (SELECT VALUE
4 FROM v$parameter
5 WHERE NAME = ‘user_dump_dest’) a,
6 (SELECT SUBSTR (VALUE, -6, 1) symbol
7 FROM v$parameter
8 WHERE NAME = ‘user_dump_dest’) b,
9 (SELECT instance_name FROM v$instance) c,
10 (SELECT spid
11 FROM v$session s, v$process p, v$mystat m
12 WHERE s.paddr = p.addr AND s.SID = m.SID AND m.statistic# = 0) d
13 /
TRACE_FILE
——————————————————————————–
F:\ORACLE\PRODUCT\10.2.0\ADMIN\ORCL\UDUMP\orcl_ora_10076.trc
11g使用如下脚本,得到转储文件
SQL> SELECT VALUE FROM V$DIAG_INFO WHERE NAME = “Default Trace File”;
VALUE
——————————————————————————-
F:\ORACLE\PRODUCT\10.2.0\ADMIN\ORCL\UDUMP\orcl_ora_10076.trc
既然已经找到了转储文件,那就看看转储文件的内容:
Start dump data blocks tsn: 7 file#: 6 minblk 41 maxblk 41 –转储的文件号和块号
buffer tsn: 7 rdba: 0x01800029 (6/41)
scn: 0x0000.000bddf9 seq: 0x01 flg: 0x04 tail: 0xddf92001
frmt: 0x02 chkval: 0x4b0e type: 0x20=FIRST LEVEL BITMAP BLOCK –这个表示它是一级位图块,直接和数据块打交道
Hex dump of block: st=0, typ_found=1
Dump of First Level Bitmap Block
——————————–
nbits : 2 nranges: 1 parent dba: 0x0180002a poffset: 0 –标红的表示它的父级位图块
unformatted: 12 total: 16 first useful block: 3
owning instance : 1
instance ownership changed at 04/20/2013 12:08:33
Last successful Search 04/20/2013 12:08:33
Freeness Status: nf1 0 nf2 1 nf3 0 nf4 0
Extent Map Block Offset: 4294967295
First free datablock : 3
Bitmap block lock opcode 0
Locker xid: : 0x0000.000.00000000
Inc #: 0 Objd: 52590
HWM Flag: HWM Set
Highwater:: 0x0180002d ext#: 0 blk#: 4 ext size: 16
#blocks in seg. hdr’s freelists: 0
#blocks below: 1
mapblk 0x00000000 offset: 0
——————————————————–
DBA Ranges :
——————————————————–
0x01800029 Length: 16 Offset: 0
0:Metadata 1:Metadata 2:Metadata 3:25-50% free
4:unformatted 5:unformatted 6:unformatted 7:unformatted
8:unformatted 9:unformatted 10:unformatted 11:unformatted
12:unformatted 13:unformatted 14:unformatted 15:unformatted
——————————————————–
End dump data blocks tsn: 7 file#: 6 minblk 41 maxblk 41
-标红的这些块被用来存储元数据,其余的块用来存储数据,注意到总共有16个块,full表示数据块已经用完了。
如果这些块用完的话,oracle就会使用二级位图结构,以此类推,会出现三级位图结构,oracle目前支持到三级位图结构,上面的表示这些块暂时未被使用,根据以上的说明oracle会从第4个块(44)开始存放索引,索引的branch_code应该放在第44块中,接下来我们就分析索引是如何分布的。
下面是一个rdba转换为具体的文件和块号的方法,后面会用到。
create or replace function getbfno(p_dba in varchar2)
return varchar2
is
l_str varchar2(255) default null;
begin
l_str :=’datafile# is:’
||dbms_utility.data_block_address_file(TO_NUMBER(LTRIM(P_DBA,’0x’),’xxxxxxxx’))
||chr(10) ||’datablock is:’
||dbms_utility.data_block_address_block(TO_NUMBER(LTRIM(P_DBA,’0x’),’xxxxxxxx’));
return l_str;
end;
–去上面的rdba得到如下的转换
SQL> select getbfno(‘0x01800029’) from dual;
GETBFNO(‘0X01800029’)
———————————————
datafile# is:6
datablock is:41
在开始插入索引前,我们下去看看 parent dba: 0x0180002a 这个里面有什么东西,也就是是二级位图块。
SQL> select getbfno(‘0x0180002a’) from dual;
GETBFNO(‘0X0180002A’)
————————————————-
datafile# is:6
datablock is:42
我们看到二级位图块是第42块,转储42块得到以下内容
*** 2013-04-20 15:30:52.140
*** SERVICE NAME:(SYS$USERS) 2013-04-20 15:30:52.125
*** SESSION ID:(121.797) 2013-04-20 15:30:52.125
Start dump data blocks tsn: 7 file#: 6 minblk 42 maxblk 42
buffer tsn: 7 rdba: 0x0180002a (6/42)
scn: 0x0000.000bddf2 seq: 0x01 flg: 0x04 tail: 0xddf22101
frmt: 0x02 chkval: 0x4be9 type: 0x21=SECOND LEVEL BITMAP BLOCK –表示它是二级位图
Hex dump of block: st=0, typ_found=1
Dump of memory from 0x09142200 to 0x09144200
9142200 0000A221 0180002A 000BDDF2 04010000 [!…*………..]
9142210 00004BE9 00000000 00000000 00000000 [.K…………..]
9142220 00000000 00000000 00000000 00000000 […………….]
Repeat 1 times
9142240 00000000 00000000 00000000 0180002B […………+…]
9142250 00000001 00000001 00000000 00000000 […………….]
9142260 00000000 00000000 0000CD6E 00000001 [……..n…….]
9142270 00000000 01800029 00010005 00000000 [….)………..]
9142280 00000000 00000000 00000000 00000000 […………….]
Repeat 502 times
91441F0 00000000 00000000 00000000 DDF22101 [………….!..]
Dump of Second Level Bitmap Block
number: 1 nfree: 1 ffree: 0 pdba: 0x0180002b –此处它指向了43块
Inc #: 0 Objd: 52590
opcode:0
xid:
L1 Ranges :
——————————————————–
0x01800029 Free: 5 Inst: 1 –在此处它指向了25块,
这一块用来记录那些位图块管理单元被包含在对象中
——————————————————–
End dump data blocks tsn: 7 file#: 6 minblk 42 maxblk 42
在上面看到一块,其中pdba指向了43块,不明白是做什么的,顺便看一下
Dump of Second Level Bitmap Block
number: 1 nfree: 1 ffree: 0 pdba: 0x0180002b –此处它指向了43块
Inc #: 0 Objd: 52590
opcode:0
43块的内容经过转储内容如下:(中间省略了一部分省略了)
*** 2013-04-20 15:40:25.406
Start dump data blocks tsn: 7 file#: 6 minblk 43 maxblk 43
buffer tsn: 7 rdba: 0x0180002b (6/43)
scn: 0x0000.000bddf9 seq: 0x01 flg: 0x04 tail: 0xddf92301
frmt: 0x02 chkval: 0x66e9 type: 0x23=PAGETABLE SEGMENT HEADER
Hex dump of block: st=0, typ_found=1
Extent Control Header
—————————————————————–
Extent Header:: spare1: 0 spare2: 0 #extents: 1 #blocks: 16
last map 0x00000000 #maps: 0 offset: 2716
Highwater:: 0x0180002d ext#: 0 blk#: 4 ext size: 16
#blocks in seg. hdr’s freelists: 0
#blocks below: 1
mapblk 0x00000000 offset: 0
Unlocked
——————————————————–
Low HighWater Mark :
Highwater:: 0x0180002d ext#: 0 blk#: 4 ext size: 16
#blocks in seg. hdr’s freelists: 0
#blocks below: 1
mapblk 0x00000000 offset: 0
Level 1 BMB for High HWM block: 0x01800029 –此处标注了高低水位线
Level 1 BMB for Low HWM block: 0x01800029
——————————————————–
Segment Type: 2 nl2: 1 blksz: 8192 fbsz: 0
L2 Array start offset: 0x00001434
First Level 3 BMB: 0x00000000
L2 Hint for inserts: 0x0180002a
Last Level 1 BMB: 0x01800029
Last Level II BMB: 0x0180002a
Last Level III BMB: 0x00000000
Map Header:: next 0x00000000 #extents: 1 obj#: 52590 flag: 0x10000000
Inc # 0
Extent Map
—————————————————————–
0x01800029 length: 16
Auxillary Map
——————————————————–
Extent 0 : L1 dba: 0x01800029 Data dba: 0x0180002c
–如果已经有空间被使用,则会指出该空间的第一个数据块的位置,可以通过此处找到branch_code
——————————————————–
Second Level Bitmap block DBAs –此处记录了二级位图的地址
——————————————————–
DBA 1: 0x0180002a
End dump data blocks tsn: 7 file#: 6 minblk 43 maxblk 43
这一块应该叫做三级位图,如果PAGETABLE SEGMENT HEADER的空间不足以存储二级位图块的指针,那么新的三级位图被创建。
关于oracle的段空间的分配大概有个了解以后,我们开始做索引的物理存放:
运行以下过程,在表中插入数据后,我们来看看索引存储的变化
SQL> declare
2 begin
3 for i in 1..10000
4
5 loop
6
7 insert into test values (i,’100′);
8 commit;
9 end loop ;
10
11 end;
12
13 /
PL/SQL 过程已成功完成。
–在查看发现索引使用了两个区间
SQL> select file_id,extent_id,block_id from dba_extents where segment_name=’IDX_
TEST’ ;
FILE_ID EXTENT_ID BLOCK_ID
———- ———- ———-
6 0 41
6 1 57
SQL> analyze index idx_test validate structure;
索引已分析
SQL> select btree_space,used_space,pct_used,blocks,lf_blks,br_blks,height from index_stats;
BTREE_SPACE USED_SPACE PCT_USED BLOCKS LF_BLKS BR_BLKS HEIGHT
———– ———- ———- ———- ———- ———- ———-
160032 149999 94 32 19 1 2
–转储41看到了数据块全部被使用了
——————————————————–
DBA Ranges :
——————————————————–
0x01800029 Length: 16 Offset: 0
0:Metadata 1:Metadata 2:Metadata 3:FULL
4:FULL 5:FULL 6:FULL 7:FULL
8:FULL 9:FULL 10:FULL 11:FULL
12:FULL 13:FULL 14:FULL 15:FULL
——————————————————–
End dump data blocks tsn: 7 file#: 6 minblk 41 maxblk 41
–转储57的文件看到以下的内容,自己分析一下吧
Dump of First Level Bitmap Block
——————————–
nbits : 2 nranges: 1 parent dba: 0x0180002a poffset: 1
unformatted: 0 total: 16 first useful block: 1
owning instance : 1
instance ownership changed at 04/20/2013 16:00:48
Last successful Search 04/20/2013 16:00:48
Freeness Status: nf1 0 nf2 9 nf3 0 nf4 0
Extent Map Block Offset: 4294967295
First free datablock : 3
Bitmap block lock opcode 0
Locker xid: : 0x0000.000.00000000
Inc #: 0 Objd: 52590
HWM Flag: HWM Set
Highwater:: 0x01800049 ext#: 1 blk#: 16 ext size: 16
#blocks in seg. hdr’s freelists: 0
#blocks below: 28
mapblk 0x00000000 offset: 1
——————————————————–
DBA Ranges :
——————————————————–
0x01800039 Length: 16 Offset: 0
0:Metadata 1:FULL 2:FULL 3:25-50% free
4:25-50% free 5:25-50% free 6:25-50% free 7:25-50% free
8:25-50% free 9:25-50% free 10:25-50% free 11:25-50% free
12:FULL 13:FULL 14:FULL 15:FULL
——————————————————–
End dump data blocks tsn: 7 file#: 6 minblk 57 maxblk 57
现在我们要转储43块,去看一下每一个区的第一个数据块的编号
Auxillary Map
——————————————————–
Extent 0 : L1 dba: 0x01800029 Data dba: 0x0180002c
Extent 1 : L1 dba: 0x01800039 Data dba: 0x0180003a
——————————————————–
SQL> select getbfno(‘0x0180002c’) from dual;
GETBFNO(‘0X0180002C’)
————————————————–
datafile# is:6
datablock is:44
44块中的内容如下:(截取一部分)
Block header dump: 0x0180002c
Object id on Block? Y
seg/obj: 0xcd6e csc: 0x00.c43b8 itc: 1 flg: E typ: 2 – INDEX
brn: 0 bdba: 0x1800029 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0004.018.00000189 0x00800639.0165.02 C— 0 scn 0x0000.000c43b7
Branch block dump
=================
header address 152314444=0x914224c
kdxcolev 1
KDXCOLEV Flags = – – –
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=— is converted=Y
kdxconco 2
kdxcosdc 1
kdxconro 18
kdxcofbo 64=0x40
kdxcofeo 7898=0x1eda
kdxcoavs 7834
kdxbrlmc 25165871=0x180002f
kdxbrsno 17
kdxbrbksz 8060
kdxbr2urrc 0
row#0[8051] dba: 25165872=0x1800030 –从这可以看到叶子节点的存储位置
col 0; len 3; (3): c2 06 2a
col 1; TERM
row#1[8042] dba: 25165873=0x1800031
col 0; len 3; (3): c2 0b 4b
col 1; TERM
row#2[8033] dba: 25165874=0x1800032
col 0; len 3; (3): c2 11 08
col 1; TERM
row#3[8024] dba: 25165875=0x1800033
col 0; len 3; (3): c2 16 29
col 1; TERM
row#4[8015] dba: 25165876=0x1800034
col 0; len 3; (3): c2 1b 4a
col 1; TERM
row#5[8006] dba: 25165877=0x1800035
col 0; len 3; (3): c2 21 07
col 1; TERM
row#6[7997] dba: 25165878=0x1800036
col 0; len 3; (3): c2 26 28
col 1; TERM
看到这儿要了解一下dump这个函数
dump 函数能查看表中列在datafile存储内容。
Oracle的NUMBER类型最多由三个部分构成,这三个部分分别是最高位表示位、数据部分、符号位。其中负数包含符号位,正数不会包括符号位(10进制即102)。另外,数值0比较特殊,它只包含一个数值最高位表示位80(16进制),没有数据部分。
DUMP函数的输出格式类似:
类型 <[长度]>,符号/指数位 [数字1,数字2,数字3,……,数字20]
各位的含义如下:
1.类型: Number型,Type=2 (类型代码可以从Oracle的文档上查到)
2.长度:指存储的字节数
3.符号/指数位
在存储上,Oracle对正数和负数分别进行存储转换:
正数:加1存储(为了避免Null)
负数:被101减,如果总长度小于21个字节,最后加一个102(是为了排序的需要)
指数位换算:
正数:指数=符号/指数位 – 193 (最高位为1是代表正数)
负数:指数=62 – 第一字节
4.从<数字1>开始是有效的数据位
从<数字1>开始是最高有效位,所存储的数值计算方法为:
将下面计算的结果加起来:
每个<数字位>乘以100^(指数-N) (N是有效位数的顺序位,第一个有效位的N=0)
举例说明
SQL> select dump(123456.789) from dual; DUMP(123456.789)
-------------------------------
Typ=2 Len=6: 195,13,35,57,79,91 |
<指数>: 195 – 193 = 2
<数字1> 13 – 1 = 12 *100^(2-0) 120000
<数字2> 35 – 1 = 34 *100^(2-1) 3400
<数字3> 57 – 1 = 56 *100^(2-2) 56
<数字4> 79 – 1 = 78 *100^(2-3) .78
<数字5> 91 – 1 = 90 *100^(2-4) .009
123456.789
SQL> select dump(-123456.789) from dual; DUMP(-123456.789)
----------------------------------
Typ=2 Len=7: 60,89,67,45,23,11,102 |
<指数> 62 – 60 = 2(最高位是0,代表为负数)
<数字1> 101 – 89 = 12 *100^(2-0) 120000
<数字2> 101 – 67 = 34 *100^(2-1) 3400
<数字3> 101 – 45 = 56 *100^(2-2) 56
<数字4> 101 – 23 = 78 *100^(2-3) .78
<数字5> 101 – 11 = 90 *100^(2-4) .009
123456.789(-)
现在我们查看叶子节点
SQL> select getbfno(‘0x1800030’) from dual;
GETBFNO(‘0X1800030’)
———————————————–
datafile# is:6
datablock is:48
转储一下48号数据块可以看到:
Block header dump: 0x01800030
Object id on Block? Y
seg/obj: 0xcd6e csc: 0x00.c16d6 itc: 2 flg: E typ: 2 – INDEX
brn: 0 bdba: 0x1800029 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0001.026.00000179 0x00800012.00cf.01 -BU- 1 fsc 0x0000.000c16d9
0x02 0x0000.000.00000000 0x00000000.0000.00 —- 0 fsc 0x0000.00000000
Leaf block dump
===============
header address 152314468=0x9142264
kdxcolev 0
KDXCOLEV Flags = – – –
kdxcolok 1
kdxcoopc 0x80: opcode=0: iot flags=— is converted=Y
kdxconco 2
kdxcosdc 2
kdxconro 533
kdxcofbo 1102=0x44e
kdxcofeo 1112=0x458
kdxcoavs 10
kdxlespl 0
kdxlende 0
kdxlenxt 25165873=0x1800031
kdxleprv 25165871=0x180002f
kdxledsz 0
kdxlebksz 8036
row#0[1112] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 2a
col 1; len 6; (6): 01 80 00 1c 02 1c
row#1[1125] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 2b
col 1; len 6; (6): 01 80 00 1c 02 1d
row#2[1138] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 2c
col 1; len 6; (6): 01 80 00 1c 02 1e
row#3[1151] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 2d
col 1; len 6; (6): 01 80 00 1c 02 1f
row#4[1164] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 2e
col 1; len 6; (6): 01 80 00 1c 02 20后面省略
注意到以下:
row#0[8051] dba: 25165872=0x1800030 –从这可以看到叶子节点的存储位置
col 0; len 3; (3): c2 06 2a –541
col 1; TERM
row#0[1112] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 2a –541
col 1; len 6; (6): 01 80 00 1c 02 1c –rowid
分支节点存放了第一个叶子节点的值,这样在进行范围判断时候直接使用叶子节点中的值来判断值位于哪一个块中。
删除541这一行数据
row#0[1112] flag: —D–, lock: 2, len=13 –表示数据已经被删除
col 0; len 3; (3): c2 06 2a
col 1; len 6; (6): 01 80 00 1c 02 1c
row#0[8051] dba: 25165872=0x1800030
col 0; len 3; (3): c2 06 2a
col 1; TERM
发现branch_code并没有删除,还是指向了它,并且在叶子节点并没有删除它,所以当DELETE 和 insert 很频繁的时候,索引会越来越庞大。
因为使用DELETE删除数据并不会删除索引,只会标记为索引无效。
下面我们来看这一行
row#1[1125] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 2b — 542
col 1; len 6; (6): 01 80 00 1c 02 1d
我们来更新这一行 ,将他的值变为545 看看索引会发生什么变化。
SQL> update test set id=’545′ where id=’542′;
已更新 1 行。
row#0[8023] flag: —D–, lock: 2, len=13
col 0; len 3; (3): c2 06 2b
col 1; len 6; (6): 01 80 00 1c 02 1d
并且增加了一行
row#3[1112] flag: ——, lock: 2, len=13
col 0; len 3; (3): c2 06 2e
col 1; len 6; (6): 01 80 00 1c 02 1d
rowid是完全一样的,同时发现一个问题
前面删除的哪一行也不在了,541哪一行的索引没有在出现,难道使用UPADATE 运算会删除以前被删除了的索引,这个是个疑问,在去试一下。好像是这样的,这个问题还有待研究。
Leaf block dump
===============
header address 152314468=0x9142264
kdxcolev 0
KDXCOLEV Flags = – – –
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=— is converted=Y
kdxconco 2
kdxcosdc 2
kdxconro 533
kdxcofbo 1102=0x44e
kdxcofeo 1112=0x458
kdxcoavs 10
kdxlespl 0
kdxlende 1
kdxlenxt 25165873=0x1800031
kdxleprv 25165871=0x180002f
kdxledsz 0
kdxlebksz 8036
row#0[8023] flag: —D–, lock: 2, len=13 –头变成这样了
col 0; len 3; (3): c2 06 2c –这一行被我update掉了,但是前面刚刚废弃的索引不见了。
col 1; len 6; (6): 01 80 00 1c 02 1e
row#1[8010] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 2d
col 1; len 6; (6): 01 80 00 1c 02 1f
row#2[7997] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 2e
这个问题希望大家共同探讨。。。。
索引的update和delete操作就是这样的,下面我们来做索引的分裂实验。
SQL> create table test2(id number,name varchar2(100));
表已创建。
SQL> create index idx_test2 on test2(id);
索引已创建。
SQL> declare
2 begin
3 for i in 1..540
4
5 loop
6
7 insert into test2 values (i,’100′);
8 commit;
9 end loop ;
10
11 end;
12
13 /
SQL> select file_id,extent_id,block_id from dba_extents where segment_name=’IDX_TEST2′ ;
FILE_ID EXTENT_ID BLOCK_ID
———- ———- ———-
6 0 105
转储105
Block header dump: 0x0180006c
Object id on Block? Y
seg/obj: 0xcd7a csc: 0x00.c9631 itc: 2 flg: E typ: 2 – INDEX
brn: 0 bdba: 0x1800069 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0000.000.00000000 0x00000000.0000.00 —- 0 fsc 0x0000.00000000
0x02 0x0008.002.000001bd 0x00800603.0124.0c –U- 1 fsc 0x0000.000c9632
Leaf block dump
===============
header address 152314468=0x9142264
kdxcolev 0
KDXCOLEV Flags = – – –
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=— is converted=Y
kdxconco 2
kdxcosdc 0
kdxconro 500
kdxcofbo 1036=0x40c
kdxcofeo 1640=0x668
kdxcoavs 604
kdxlespl 0
kdxlende 0
kdxlenxt 0=0x0
kdxleprv 0=0x0
kdxledsz 0
kdxlebksz 8036
row#0[8024] flag: ——, lock: 0, len=12
col 0; len 2; (2): c1 02
col 1; len 6; (6): 01 80 00 5c 00 00
row#1[8012] flag: ——, lock: 0, len=12
col 0; len 2; (2): c1 03
col 1; len 6; (6): 01 80 00 5c 00 01
此时root和leaf在同一个块中。
SQL> insert into test2 values(5,’100′);
已创建 1 行。
发生了分裂
Block header dump: 0x0180006c
Object id on Block? Y
seg/obj: 0xcd7e csc: 0x00.c9fb3 itc: 1 flg: E typ: 2 – INDEX
brn: 0 bdba: 0x1800069 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000a.017.00000194 0x008000a0.011a.01 -BU- 1 fsc 0x0000.000c9fb5
Branch block dump
=================
header address 152314444=0x914224c
kdxcolev 1
KDXCOLEV Flags = – – –
kdxcolok 1
kdxcoopc 0x80: opcode=0: iot flags=— is converted=Y
kdxconco 2
kdxcosdc 1
kdxconro 1
kdxcofbo 30=0x1e
kdxcofeo 8051=0x1f73
kdxcoavs 8021
kdxbrlmc 25165935=0x180006f
kdxbrsno 0
kdxbrbksz 8060
kdxbr2urrc 0
row#0[8051] dba: 25165936=0x1800070
col 0; len 3; (3): c2 03 50 –349
col 1; TERM
—– end of branch block dump —–
End dump data blocks tsn: 7 file#: 6 minblk 108 maxblk 108
SQL> select getbfno(‘0x1800070’) from dual;
GETBFNO(‘0X1800070’)
———————————————–
datafile# is:6
datablock is:112
此时我们分别转储111和112数据文件
111文件如下
Leaf block dump
===============
header address 152314468=0x9142264
kdxcolev 0
KDXCOLEV Flags = – – –
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=— is converted=Y
kdxconco 2
kdxcosdc 1
kdxconro 279
kdxcofbo 594=0x252
kdxcofeo 4499=0x1193
kdxcoavs 3917
kdxlespl 0
kdxlende 0
kdxlenxt 25165936=0x1800070
kdxleprv 0=0x0
kdxledsz 0
kdxlebksz 8036
row#0[4511] flag: ——, lock: 0, len=12
col 0; len 2; (2): c1 02
col 1; len 6; (6): 01 80 00 5c 00 00
row#1[4523] flag: ——, lock: 0, len=12
col 0; len 2; (2): c1 03
col 1; len 6; (6): 01 80 00 5c 00 01
row#2[4535] flag: ——, lock: 0, len=12
col 0; len 2; (2): c1 04
col 1; len 6; (6): 01 80 00 5c 00 02
row#3[4547] flag: ——, lock: 0, len=12
col 0; len 2; (2): c1 05
col 1; len 6; (6): 01 80 00 5c 00 03
row#4[4559] flag: ——, lock: 0, len=12
col 0; len 2; (2): c1 06 –引起了拆分
col 1; len 6; (6): 01 80 00 5c 00 04
row#5[4499] flag: ——, lock: 2, len=12
col 0; len 2; (2): c1 06
col 1; len 6; (6): 01 80 00 5c 02 1c
row#6[4583] flag: ——, lock: 0, len=12
col 0; len 2; (2): c1 07
col 1; len 6; (6): 01 80 00 5c 00 05
row#7[4595] flag: ——, lock: 0, len=12
col 0; len 2; (2): c1 08
。。。。。。。
row#278[8023] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 03 4f
col 1; len 6; (6): 01 80 00 5c 01 15
—– end of leaf block dump —–
End dump data blocks tsn: 7 file#: 6 minblk 111 maxblk 111
转储112块
Block header dump: 0x01800070
Object id on Block? Y
seg/obj: 0xcd7e csc: 0x00.c9fb4 itc: 2 flg: E typ: 2 – INDEX
brn: 0 bdba: 0x1800069 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000a.017.00000194 0x00800131.011b.02 -B– 1 fsc 0x0000.00000000
0x02 0x0000.000.00000000 0x00000000.0000.00 —- 0 fsc 0x0000.00000000
Leaf block dump
===============
header address 152314468=0x9142264
kdxcolev 0
KDXCOLEV Flags = – – –
kdxcolok 1
kdxcoopc 0x87: opcode=7: iot flags=— is converted=Y
kdxconco 2
kdxcosdc 1
kdxconro 262
kdxcofbo 560=0x230
kdxcofeo 4633=0x1219
kdxcoavs 4073
kdxlespl 0
kdxlende 0
kdxlenxt 0=0x0
kdxleprv 25165935=0x180006f
kdxledsz 0
kdxlebksz 8036
row#0[4633] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 03 50
col 1; len 6; (6): 01 80 00 5c 01 16
row#1[4646] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 03 51
col 1; len 6; (6): 01 80 00 5c 01 17
row#2[4659] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 03 52
col 1; len 6; (6): 01 80 00 5c 01 18
row#3[4672] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 03 53
col 1; len 6; (6): 01 80 00 5c 01 19
row#4[4685] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 03 54
col 1; len 6; (6): 01 80 00 5c 01 1a
row#5[4698] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 03 55
col 1; len 6; (6): 01 80 00 5c 01 1b
。。。。。。
row#256[7958] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 24
col 1; len 6; (6): 01 80 00 5c 02 16
row#257[7971] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 25
col 1; len 6; (6): 01 80 00 5c 02 17
row#258[7984] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 26
col 1; len 6; (6): 01 80 00 5c 02 18
row#259[7997] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 27
col 1; len 6; (6): 01 80 00 5c 02 19
row#260[8010] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 28
col 1; len 6; (6): 01 80 00 5c 02 1a
row#261[8023] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 06 29
col 1; len 6; (6): 01 80 00 5c 02 1b
—– end of leaf block dump —–
End dump data blocks tsn: 7 file#: 6 minblk 112 maxblk 112
每个块大约各占一半,这就是所谓的50-50拆分
下面我们看看99-1拆分
当我们给表中插入从最大值开始递增的值,当插入值754时,发生了分裂,108块中的信息如下:增加了0x1800071
row#0[8051] dba: 25165936=0x1800070
col 0; len 3; (3): c2 03 50
col 1; TERM
row#1[8042] dba: 25165937=0x1800071 -113
col 0; len 3; (3): c2 08 55
col 1; TERM
—– end of branch block dump —–
End dump data blocks tsn: 7 file#: 6 minblk 108 maxblk 108
我们转储113块的内容
row#0[8023] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 08 55
col 1; len 6; (6): 01 80 00 5f 00 f6
发现只含有最后的值,没有想5050分裂那样,各有一半。
以上就是关于b*树索引的学习,参照了别人很多的东西,感觉自己受益匪浅。其中还有一些错误或者疑问需要大家共同探讨。。。