索引是我们经常使用的一种数据库搜索优化手段。适当的业务操作场景使用适当的索引方案可以显著的提升系统整体性能和用户体验。在Oracle中,索引有包括很多类型。不同类型的索引适应不同的系统环境和访问场景。其中,唯一性索引Unique Index是我们经常使用到的一种。
唯一性索引unique index和一般索引normal index最大的差异就是在索引列上增加了一层唯一约束。添加唯一性索引的数据列可以为空,但是只要存在数据值,就必须是唯一的。
那么,在使用唯一性索引时,同一般索引有什么差异呢?下面通过一系列的演示实验来说明。
1、实验环境准备
为了体现出一致性和可能的差异性,选择相同的数据值列加入不同类型的索引结构。
声明:本篇知识方法受到dbsnake相关文章启发,特此感谢!
在本系列的前篇(/17203031/viewspace-700089)里,我们探讨了唯一索引和普通索引在应用角度上的差异。实验中,我们发现在基础数据相同的情况下,两类型索引在体积上有细微的差异,这使得我们可以猜测两种类型索引在存储结构上的可能差异。
本篇打算从存储结构入手,探讨两种类型索引的差异。
1、 环境准备
同前篇相同,准备相同的数据索引列取值,创建不同类型的索引。为了减少整理数据量,所以选择较小的数据集。
SQL> select * from v$version where rownum<2;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
SQL> create table t (id number, vid number);
Table created
SQL> select * from t;
ID VID
---------- ----------
1 1
2 2
3 3
//创建普通索引
SQL> create index idx_t_normalid on t(id);
Index created
//创建唯一性索引
SQL> create unique index idx_t_uniqueid on t(vid);
Index created
SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);
PL/SQL procedure successfully completed
SQL> col object_name for a20;
SQL> select object_name, object_id from dba_objects where object_name in ('IDX_T_NORMALID','IDX_T_UNIQUEID');
OBJECT_NAME OBJECT_ID
-------------------- ----------
IDX_T_NORMALID 75141
IDX_T_UNIQUEID 75142
2、 普通索引逻辑结构分析
思路是从索引树入手,现将索引段结构Dump出去,检查整体分布情况。
//获取跟踪文件位置
SQL> select name, value from v$diag_info where name='Default Trace File';
NAME VALUE
------------------------------ --------------------------------------------------------------------------------
Default Trace File /u01/diag/rdbms/wilson/wilson/trace/wilson_ora_5850.trc
//将索引树Dump出到Trace文件
SQL> alter session set events 'immediate trace name treedump level 75141';
Session altered
注意:v$diag_info视图是Oracle 11g中新推出的视图类型,其中包括了所有诊断文件的位置和目录。其中Default Trace File项目就是当前会话对应的Trace File路径和名称,不需要我们过去那样使用自定义函数查询。
此时,我们检索Trace File内容,核心片段如下:
*** 2011-06-15 01:47:04.350
*** SESSION ID:(20.13) 2011-06-15 01:47:04.350
*** CLIENT ID:() 2011-06-15 01:47:04.350
*** SERVICE NAME:(wilson) 2011-06-15 01:47:04.350
*** MODULE NAME:(PL/SQL Developer) 2011-06-15 01:47:04.350
*** ACTION NAME:(Command Window - New) 2011-06-15 01:47:04.350
----- begin tree dump
leaf: 0x415af1 4283121 (0: nrow: 3 rrow: 3)
----- end tree dump
上面的Tree Dump结果,说明因为索引很小(只有三行记录),只有一个索引块中包括数据。其中叶子节点为3个,该索引块的地址为(0x415af1,十进制表示为4283121)。
索引块4283121对应的实际文件和数据块信息是什么呢?使用dbms_utiliy包的相关方法,可以帮助获取到对应文件和块号。
SQL> select dbms_utility.data_block_address_file(4283121), dbms_utility. data_block_address_block(4283121) from dual;
DBMS_UTILITY.DATA_BLOCK_ADDRES DBMS_UTILITY.DATA_BLOCK_ADDRES
------------------------------ ------------------------------
1 88817
索引块在文件1,对应块号为88817。
使用逻辑dump出块的结构信息。
SQL> alter system dump datafile 1 block 88817;
System altered
DUMP在跟踪文件上的核心内容为:
--核心片段
Block dump from cache:
Dump of buffer cache at level 4 for tsn=0, rdba=4283121
BH (0x2dff3578) file#: 1 rdba: 0x00415af1 (1/88817) class: 1 ba: 0x2dea0000
Block header dump: 0x00415af1
Object id on Block? Y
seg/obj: 0x12585 csc: 0x00.396f48 itc: 2 flg: - typ: 2 - INDEX
fsl: 0 fnx: 0x0 ver: 0x01
row#0[8020] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 02
col 1; len 6; (6): 00 41 5a e9 00 00
row#1[8008] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 03
col 1; len 6; (6): 00 41 5a e9 00 01
row#2[7996] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 04
col 1; len 6; (6): 00 41 5a e9 00 02
----- end of leaf block dump -----
End dump data blocks tsn: 0 file#: 1 minblk 88817 maxblk 88817
实际Dump出的信息很多,篇幅原因,笔者只是将最核心的部分加以展现。
首先关注最后的三个row#信息,该部分表示三个叶子节点的核心内容。其中[]中包括的是叶子节点在索引块中的相对偏移量。Len表示该叶子所占用空间长度是多少(注意:此处的Normal Index叶子节点占用12长度)。
最后,观察行与行之间的偏移量差(7996-8008-8020),正好是行间的距离12位。
每个叶子节点包括两个column内容,分别占据2位和6位。由于是使用16进制形式保存,我们不能直接看出内容含义。下面实验可以帮助我们进行猜测:
--关键数据演算:
SQL> select dump(1,1016) from dual;
DUMP(1,1016)
-----------------
Typ=2 Len=2: c1,2
SQL> select dump(2,1016) from dual;
DUMP(2,1016)
-----------------
Typ=2 Len=2: c1,3
SQL> select dump(3,1016) from dual;
DUMP(3,1016)
-----------------
Typ=2 Len=2: c1,4
我们的索引列取值就是1、2、3、4,上面使用DUMP函数的结果,告诉我们跟踪文件中每个row的第一列表示的是索引列取值。索引叶子节点内容无非就是rowid和索引键值。那么row的第二列含义必然可能就是rowid相关内容。
只对第三行数据进行试算:
16进制取值:00 41 5a e9 00 02
2进制取值:00000000 01000001 01011010 11101001 00000000 00000010
根据相关资料,我们进行如下设定:
File_ID:前10位:00000000 01=1
Block_id 中间22位:000001 01011010 11101001=88809
Slot_id:00000000 00000010=2
如果改值是rowid,那么一定和数据表T行的rowid存在对应关系。
SQL> select t.*,, dbms_rowid.rowid_relative_fno() fno, dbms_rowid.rowid_block_number()
blockn,dbms_rowid.rowid_row_number() rown from t;
ID VID ROWID FNO BLOCKN ROWN
---------- ---------- ------------------ ---------- ---------- ----------
1 1 AAASWCAABAAAVrpAAA 1 88809 0
2 2 AAASWCAABAAAVrpAAB 1 88809 1
3 3 AAASWCAABAAAVrpAAC 1 88809 2
Rowid中包括的信息:文件编号+对象编号+块编号+块内slot编号;索引叶子中的第二列数据,已经能够提供文件编号、块编号和内部slot编号。对象编号是进行选取Select时候就带入的。
3、普通索引物理结构分析
从上面的逻辑结构,我们看到了存储的逻辑结构。下面根据信息,我们到物理存储层面看普通索引结构。注意,此处我们只关注一行数据,就是row2[7996]的叶子节点。
我们现在有该索引的文件编号和块编号,缺乏就是实际的偏移量。下面首先计算出实际的偏移量。
SQL> select file_name, tablespace_name from dba_data_files where file_id=1;
FILE_NAME TABLESPACE_NAME
-------------------------------------------------------------------------------- ------------------------------
/u01/oradata/WILSON/datafile/o1_mf_system_6bcsnqfc_.dbf SYSTEM
SQL> select tablespace_name,SEGMENT_SPACE_MANAGEMENT from dba_tablespaces where tablespace_name='SYSTEM';
TABLESPACE_NAME SEGMENT_SPACE_MANAGEMENT
------------------------------ ------------------------
SYSTEM MANUAL
数据表索引对应的表空间SYSTEM,是使用MSSM进行Segment Space Management。所以,计算出的偏移量就是:
7996+68+(2-1)*24=8088
下面使用BBED工具直接进行块读取,注意,首先需要关闭数据库。
==将dump筷
[oracle@oracle11g test]$ bbed parfile=
Password:
BBED: Release 2.0.0.0.0 - Limited Production on Wed Jun 15 02:05:20 2011
Copyright (c) 1982, 2009, Oracle and/or its affiliates. All rights reserved.
************* !!! For Oracle Internal Use only !!! ***************
BBED> info
File# Name Size(blks)
----- ---- ----------
1 /u01/oradata/WILSON/datafile/o1_mf_system_6bcsnqfc_.dbf 106240
(篇幅原因,有省略……)
//定位到指定的文件和块位置(也可以使用set file 1 block 88817)
BBED> set dba 0x00415af1
DBA 0x00415af1 (4283121 1,88817)
//定位块内的偏移量
BBED> set offset 8088
OFFSET 8088
//DUMP显示出来
BBED> dump
File: /u01/oradata/WILSON/datafile/o1_mf_system_6bcsnqfc_.dbf (1)
Block: 88817 Offsets: 8088 to 8191 Dba:0x00415af1
------------------------------------------------------------------------
000002c1 04060041 5ae90002 000002c1 03060041 5ae90001 000002c1 02060041
5ae90000 0d000040 15000002 00401500 000203c2 085eac00 01150015 00004015
00000100 40150000 0103c208 5aac0001 11001100 0040110f 00090040 110f0009
03c20858 0106496f
<32 bytes per line>
再看一下我们将row2导出的信息:
row#2[7996] flag: ------, lock: 0, len=12
col 0; len 2; (2): c1 04
col 1; len 6; (6): 00 41 5a e9 00 02
注意:两个标记0x02和0x06的作用是标记列Column信息,用于分割。
4、 局部结论
对普通索引结构来说,索引叶子节点上保存索引键值和对应数据行rowid。两者是以行中不同column的方式进行存储,键值在前,rowid在后。
下面我们使用相同的流程处理Unique Index,检查相关的存储结构。
声明:本篇知识体系受到dbsnake相关文章启发,特此感谢!
本系列的中篇(/17203031/viewspace-700163)中,我们进行了Normal Index的导出和结构分析。分析后,我们发现Normal Index叶子节点实际上是表现为两个column结构,第一列为索引列值,第二列为对应rowid。本篇中,我们以相同的方法对unique index进行研究。
1、 Unique Index逻辑结构Dump
相似,使用Treedump的方法,将索引树idx_t_uniqueid进行导出。
--Unique Index
SQL> select name, value from v$diag_info where name='Default Trace File';
NAME VALUE
-------------------- --------------------------------------------------------------------------------
Default Trace File /u01/diag/rdbms/wilson/wilson/trace/wilson_ora_6330.trc
//75142为索引idx_t_uniqueid的object_id
SQL> alter session set events 'immediate trace name treedump level 75142';
Session altered
//Trace File中的核心片段如下:
*** 2011-06-15 02:16:41.584
*** SESSION ID:(138.4) 2011-06-15 02:16:41.584
*** CLIENT ID:() 2011-06-15 02:16:41.584
*** SERVICE NAME:(wilson) 2011-06-15 02:16:41.584
*** MODULE NAME:(PL/SQL Developer) 2011-06-15 02:16:41.584
*** ACTION NAME:(Command Window - New) 2011-06-15 02:16:41.584
----- begin tree dump
leaf: 0x415af9 4283129 (0: nrow: 3 rrow: 3)
----- end tree dump
由于该索引结构很小,只包括一个叶子节点索引块。该块地址的十六进为:0x415af9,对应十进制地址为4283129。查找对应的file编号和block编号。
SQL> select dbms_utility.data_block_address_file(4283129), dbms_utility. data_block_address_block(4283129) from dual;
DBMS_UTILITY.DATA_BLOCK_ADDRES DBMS_UTILITY.DATA_BLOCK_ADDRES
------------------------------ ------------------------------
1 88825
地址4283129对应的位置为file编号1,block编号88825。
使用数据块dump的方法,将数据块dump出。
SQL> alter system dump datafile 1 block 88825;
System altered
//Trace File中的内容
Start dump data blocks tsn: 0 file#:1 minblk 88825 maxblk 88825
Block dump from cache:
Dump of buffer cache at level 4 for tsn=0, rdba=4283129
BH (0x2afeef14) file#: 1 rdba: 0x00415af9 (1/88825) class: 1 ba: 0x2adf6000
set: 5 pool 3 bsz: 8192 bsi: 0 sflg: 2 pwc: 55,19
(篇幅原因,省略部分……)
Block header dump: 0x00415af9
Object id on Block? Y
seg/obj: 0x12586 csc: 0x00.396f56 itc: 2 flg: - typ: 2 - INDEX
fsl: 0 fnx: 0x0 ver: 0x01
row#0[8021] flag: ------, lock: 0, len=11, data:(6): 00 41 5a e9 00 00
col 0; len 2; (2): c1 02
row#1[8010] flag: ------, lock: 0, len=11, data:(6): 00 41 5a e9 00 01
col 0; len 2; (2): c1 03
row#2[7999] flag: ------, lock: 0, len=11, data:(6): 00 41 5a e9 00 02
col 0; len 2; (2): c1 04
----- end of leaf block dump -----
End dump data blocks tsn: 0 file#: 1 minblk 88825 maxblk 88825
此处,我们看到了Unique Index索引叶子节点和Normal Index的差异。在Unique Index叶子节点上,每行row只对应了一个col信息(而非normal index的两个)。Col[0]中对应的是索引列的键值。而rowid被放置在了行row的头部。这点差异就意味着两种索引结构在存储构成上的确有一些差距。
下面,我们来检查一下纯物理结构,借助BBED工具。
2、Unique Index物理结构分析
至此,我们已经知道了索引叶子块所在文件和块编号,进行物理分析只需要计算额外的offset偏移量。
从对unique index索引块dump出的结果看,我们可以看到相对偏移量信息。
row#2[7999] flag: ------, lock: 0, len=11, data:(6): 00 41 5a e9 00 02
col 0; len 2; (2): c1 04
----- end of leaf block dump -----
与对Normal Index相同,我们研究第三行数据,相对偏移量是7999。由于索引idx_t_uniqueid也存在在system表空间,属于MSSM管理方式。计算块内偏移量信息:
7999+68+(2-1)*24=8091
使用BBED的要素已经获取到,进行物理分析。
//设置文件和块号
BBED> set dba 0x00415af9
DBA 0x00415af9 (4283129 1,88825)
//设置偏移量
BBED> set offset 8091
OFFSET 8091
BBED> dump
File: /u01/oradata/WILSON/datafile/o1_mf_system_6bcsnqfc_.dbf (1)
Block: 88825 Offsets: 8091 to 8191 Dba:0x00415af9
------------------------------------------------------------------------
00000041 5ae90002 02c10400 0000415a e9000102 c1030000 00415ae9 000002c1
02100000 40110e00 02004011 0e000203 c20837ac 00011300 13000040 110e0001
0040110e 000103c2 0834ac00 01060006 00004011 0d000700 40110d00 0703c208
32010657 6f
<32 bytes per line>
对应原有的dump结果,可以清晰看到索引列键值和rowid信息。
row#2[7999] flag: ------, lock: 0, len=11, data:(6): 00 41 5a e9 00 02
col 0; len 2; (2): c1 04
----- end of leaf block dump ---
加上连带的四个0,可以看到保存的方式。Rowid在行头,以0x02开头的col[0]结构,保存索引列键值。对比原有的normal index结构,可以发现差距。为便于查看,normal index的结构如下:
//Normal Index叶子节点,DUMP显示出来
BBED> dump
File: /u01/oradata/WILSON/datafile/o1_mf_system_6bcsnqfc_.dbf (1)
Block: 88817 Offsets: 8088 to 8191 Dba:0x00415af1
------------------------------------------------------------------------
000002c1 04060041 5ae90002 000002c1 03060041 5ae90001 000002c1 02060041
5ae90000 0d000040 15000002 00401500 000203c2 085eac00 01150015 00004015
00000100 40150000 0103c208 5aac0001 11001100 0040110f 00090040 110f0009
03c20858 0106496f
<32 bytes per line>
Normal Index叶子节点长度为len=12,unique index长度为len=11。差异就是在于0x06的第二列col[1]标志位。
3、结论
唯一索引和普通索引在结构上存在差异,主要表现在存储结构和方式上。两者相比,唯一索引在体积上略小一点。但是从实际应用方面,唯一索引只是比普通索引增加了列值约束。其他如执行计划、效率没有过多的差别。
此时笔者想法有两个以为:
首先,Oracle在设计唯一索引的时候,为什么要选择这样的结构?在使用的时候有什么优势所在?
其次,唯一索引没有选择隐式的约束,这种结构类型如何实现唯一的效果?
SQL> select * from v$version where rownum<2;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
SQL> create table t as select * from dba_objects;
Table created
//保证data_object_id和object_id取值相同;
SQL> update t set data_object_id=object_id;
72581 rows updated
SQL> commit;
Commit complete
//普通索引
SQL> create index idx_t_normalid on t(object_id);
Index created
//唯一性索引
SQL> create unique index idx_t_uniid on t(data_object_id);
Index created
SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);
PL/SQL procedure successfully completed
2、体积容量比较
在环境准备中,我们将索引列取值设置为完全相同,尽量避免由于外在原因引起的差异。下面我们检查数据字典中的容量比较信息。
首先是查看索引段index segment信息。
SQL> select segment_name, segment_type, bytes, blocks, extents from dba_segments where segment_name in ('IDX_T_NORMALID','IDX_T_UNIID');
SEGMENT_NAME SEGMENT_TYPE BYTES BLOCKS EXTENTS
-------------------- ------------------ ---------- ---------- ----------
IDX_T_NORMALID INDEX 983040 120 15
IDX_T_UNIID INDEX 917504 112 14
一般索引normal index较唯一性索引空间消耗略大。索引idx_t_normalid占据15个分区,120个block。略大于idx_t_uniid的14个分区块。
这个地方需要注意一下,在数据字典中一个segment的分区占据,是Oracle系统分配给的空间,并不意味着全部使用殆尽。可能两个索引结构差异很小,但是额外多分配出一个extent。
索引叶子结构上,检查数据字典内容。
SQL> select index_name, index_type, UNIQUENESS, BLEVEL, LEAF_BLOCKS, DISTINCT_KEYS from dba_indexes where index_name in ('IDX_T_NORMALID','IDX_T_UNIID');
INDEX_NAME INDEX_TYPE UNIQUENESS BLEVEL LEAF_BLOCKS DISTINCT_KEYS
-------------------- --------------- ---------- ---------- ----------- -------------
IDX_T_UNIID NORMAL UNIQUE 1 106 51330
IDX_T_NORMALID NORMAL NONUNIQUE 1 113 51330
两者的差异不大,normal index空间消耗要略大于unique index。
结论:从数据字典反映出的情况可以知道,同一般索引相比,唯一性索引的空间消耗略小一些。由于我们采用的实验数据都是相同的,这一点点的差距可能就意味着两种索引类型在存储结构上存在略微的差异。
3、违反约束实验
作为唯一性索引,在添加创建和进行dml操作的时候,都会严格发挥出约束的作用。
SQL> insert into t select * from t where rownum<2;
insert into t select * from t where rownum<2
ORA-00001: 违反唯一约束条件 (SYS.IDX_T_UNIID)
4、等号检索实验
当我们进行等号查询的时候,Oracle对两种索引生成的执行计划有何种差异?注意:下面的select检索之前,都使用flush语句对shared_pool和buffer_cache进行清理。
--精确查询
SQL> select * from t where object_id=1000;
执行计划
----------------------------------------------------------
Plan hash value: 776407697
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 101 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 101 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_T_NORMALID | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OBJECT_ID"=1000)
统计信息
----------------------------------------------------------
528 recursive calls
0 db block gets
87 consistent gets
11 physical reads
0 redo size
1200 bytes sent via SQL*Net to client
376 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> select * from t where data_object_id=1000;
执行计划
----------------------------------------------------------
Plan hash value: 335537167
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 101 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 101 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | IDX_T_UNIID | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("DATA_OBJECT_ID"=1000)
统计信息
----------------------------------------------------------
528 recursive calls
0 db block gets
86 consistent gets
10 physical reads
0 redo size
1200 bytes sent via SQL*Net to client
376 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
1 rows processed
这里,我们看到了Unique Index的一个特性,就是等号操作时执行计划的差异。对Unique Index而言,进行相等查询的结果只有一行值或者没有,所以没必要进行传统的Index Range Scan操作。此处,执行计划中使用的是Index Unique Scan操作,直接精确定位到指定的记录项目,返回rowid记录。
而一般索引在进行等号检索的时候,通常使用的就是Index Range Scan操作。沿着索引树叶子节点进行水平扫描操作,直到获取索引符合条件索引列值的rowid列表。
从成本上看,两者虽然执行计划操作方式有一定差别,但是成本实际差异不大。CPU成本和执行时间上相同。各种块读操作(逻辑读和物理读)存在一些差异,笔者认为源于两个索引结构的微量区别,这样读取的块数一定有些差异。
5、范围搜索实验
当我们进行索引列的范围搜索时,执行计划和成本有何种差异呢?
--范围匹配
SQL> select * from t where object_id>=1000 and object_id<=1500;
已选择490行。
执行计划
----------------------------------------------------------
Plan hash value: 776407697
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 485 | 48985 | 14 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 485 | 48985 | 14 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_T_NORMALID | 485 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OBJECT_ID">=1000 AND "OBJECT_ID"<=1500)
统计信息
----------------------------------------------------------
528 recursive calls
0 db block gets
158 consistent gets
17 physical reads
0 redo size
23775 bytes sent via SQL*Net to client
728 bytes received via SQL*Net from client
34 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
490 rows processed
SQL> select * from t where data_object_id>=1000 and data_object_id<=1500;
已选择490行。
执行计划
----------------------------------------------------------
Plan hash value: 2700411221
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 485 | 48985 | 14 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 485 | 48985 | 14 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_T_UNIID | 485 | | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("DATA_OBJECT_ID">=1000 AND "DATA_OBJECT_ID"<=1500)
统计信息
----------------------------------------------------------
528 recursive calls
0 db block gets
157 consistent gets
16 physical reads
0 redo size
23775 bytes sent via SQL*Net to client
728 bytes received via SQL*Net from client
34 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
490 rows processed
从实验结果看,两者在进行范围搜索时,执行计划没有差异。两者都是进行Index Range Scan操作。各类型成本基本相同。
6、结论
本篇主要从应用角度,分析Unique Index与一般normal Index的差异。从结果看,Unique Index就是额外添加的唯一性约束。该约束严格的保证索引列的取值唯一性,这在一些数据列上的业务约束实现是很重要的功能。比如一个数据列,不能作为主键,而且允许为空,但是业务上要求唯一特性。这时候使用唯一性索引就是最好的选择。
从执行计划where条件中的表现看,Unique Index和一般normal Index没有显著性的差异。
两者数据基础值一样的情况下,生成索引的体积存在略微的差异,说明在存储结构上两者可能有不同。下面我们来分析一下两类型索引的结构信息。