oracle序列的空值,浅谈索引序列之是否可以存储NULL值?

Oracle索引能否存储NULL值?询问周围的朋友,基本都认为不能存储NULL值,但也有一些持反对意见的人,认为其他数据库索引能存储NULL值,为什么ORACLE不可以?那到底实际情况什么样子?无论哪种观点,总应该找权威的观点来做理论支持,不能人云亦云(做技术的人就是这么较劲,来不得半点马虎)。在翻阅了大量资料后,搜集到相关内容如下:

1.B*Tree indexes, except in the special case of cluster B*Tree indexes, do not store completely Null entries, but bitmap and cluster indexes do.--《Expert one on Oracle》

2. Columns with many duplicate values or many rows withNULLvalues should not be included or should be the last-named columns in the index definition. --Oracle Online Documention.

对上面两条内容合并一下可以得出结论:B树索引可以存储NULL值,但不允许索引字段全部为NULL,若索引字段全部为NULL,索引不会存储。

下面我们通过实验进行说明。

Oracle版本信息:

SELECT * FROM v$version WHERE rownum=1;

BANNER

-------------------------------------------------------------------------------

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production

1.create测试表,并插入相关数据

drop table t purge;

create table t (id number,data varchar2(100));

insert into t values(1,'a');

insert into t values(2,'b');

insert into t values(3,'c');

insert into t values(100,'aaa');

insert into t values(103,'ccc');

commit;

2.创建索引

create index t_ind_id on t(id);

3.分析索引

通过analyze index分析索引

analyze index t_ind_id validate structure;

select name, lf_rows from index_stats;

NAME                              LF_ROWS

------------------------------ ----------

T_IND_ID                                5

进一步treedump索引层次结构

select OBJECT_ID,DATA_OBJECT_ID from dba_objects where owner='SCOTT' and OBJECT_NAME='T_IND_ID';

OBJECT_ID DATA_OBJECT_ID

---------- --------------

17477          17477

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

select tracefile from v$process where addr=(select paddr from v$session where sid=(select distinct sid from v$mystat));

输出结果如下:

*** 2017-02-22 03:42:20.194

----- begin tree dump

leaf: 0x1002823 16787491 (0: nrow: 5 rrow: 5)

----- end tree dump

通过trace文件可以看到索引共记录了5个条目,其中0x1002823为索引的16进制地址,16787491对应索引的10进制地址。

将地址转化为具体的数据文件和块

SELECT dbms_utility.data_block_address_file(16787491),dbms_utility.data_block_address_block(16787491) FROM dual;

DBMS_UTILITY.DATA_BLOCK_ADDRESS_FILE(16787491) DBMS_UTILITY.DATA_BLOCK_ADDRESS_BLOCK(16787491)

---------------------------------------------- -----------------------------------------------

4                                           10275

通过命令将该块的内容转储出来

alter system dump datafile 4 block 10275;

Leaf block dump

===============

header address 139905684912740=0x7f3e54a6ba64

kdxcolev 0

KDXCOLEV Flags = - - -

kdxcolok 0

kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y

kdxconco 2

kdxcosdc 0

kdxconro 5

kdxcofbo 46=0x2e

kdxcofeo 7971=0x1f23

kdxcoavs 7925

kdxlespl 0

kdxlende 0

kdxlenxt 0=0x0

kdxleprv 0=0x0

kdxledsz 0

kdxlebksz 8032

row#0[8020] flag: ------, lock: 2, len=12

col 0; len 2; (2):  c1 02

col 1; len 6; (6):  01 00 24 1c 00 00

row#1[7971] flag: ------, lock: 2, len=12

col 0; len 2; (2):  c1 03

col 1; len 6; (6):  01 00 24 1c 00 04

row#2[8008] flag: ------, lock: 2, len=12

col 0; len 2; (2):  c1 04

col 1; len 6; (6):  01 00 24 1c 00 01

row#3[7996] flag: ------, lock: 2, len=12

col 0; len 2; (2):  c2 02

col 1; len 6; (6):  01 00 24 1c 00 02

row#4[7983] flag: ------, lock: 2, len=13

col 0; len 3; (3):  c2 02 04

col 1; len 6; (6):  01 00 24 1c 00 03

----- end of leaf block dump -----

End dump data blocks tsn: 4 file#: 4 minblk 10275 maxblk 10275

其中col 0--索引的键值,col1--rowid(对于叶子节点来说),通过dbms_stats.convert_raw_value转换可以确认和实际情况一样

declare n number;

begin

dbms_stats.convert_raw_value('c102',n);

dbms_output.put_line('c102--->' || n);

dbms_stats.convert_raw_value('c103',n);

dbms_output.put_line('c103--->' || n);

dbms_stats.convert_raw_value('c104',n);

dbms_output.put_line('c104--->' || n);

dbms_stats.convert_raw_value('c202',n);

dbms_output.put_line('c202--->' || n);

dbms_stats.convert_raw_value('c20204',n);

dbms_output.put_line('c20204--->' || n);

end;

/

c102--->1

c103--->2

c104--->3

c202--->100

c20204--->103

4.上面啰啰嗦嗦写了那么多,只为证明一个问题:索引准确存储了5个记录的相关索引键值。下面进入正题

插入几条id为空的记录,再看看索引的情况

insert into t(data) values('d');

insert into t(data) values('eeeee');

insert into t(data) values('ffffff');

commit;

通过analyze index分析索引

analyze index t_ind_id validate structure;

select name, lf_rows from index_stats;

NAME                              LF_ROWS

------------------------------ ----------

T_IND_ID                                5

再次treedump索引层次结构

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

select tracefile from v$process where addr=(select paddr from v$session where sid=(select distinct sid from v$mystat));

*** 2017-02-22 03:59:08.032

leaf: 0x1002823 16787491 (0: nrow: 5 rrow: 5)

----- end tree dump

细心的你发现什么了吗?咦,为什么我刚刚插入了3条记录,索引的条目并没有增长呢?B树索引可以存储NULL值,但不允许索引字段全部为NULL,若索引字段全部为NULL,索引不会存储。 因为该索引为单列索引,且插入的数据索引字段为NULL,等于整个索引键值为NULL,不会保存,因此索引条目没有增加。

趁热打铁,我们再创建一个复合索引的例子来印证刚才的理论。

1.create测试表,并插入相关数据

drop table t1 purge;

create table t1 (id number,data varchar2(100));

insert into t1 values(1,'a');

commit;

2.创建索引

create index t1_ind_id on t1(id,data);

3.分析索引

通过analyze index分析索引

analyze index t1_ind_id validate structure;

select name, lf_rows from index_stats;

NAME                              LF_ROWS

------------------------------ ----------

T_IND_ID                                1

insert into t1(data) values('b');

commit;

analyze index t1_ind_id validate structure;

select name, lf_rows from index_stats;

NAME                              LF_ROWS

------------------------------ ----------

T1_IND_ID2

insert into t1(id) values(3);

commit;

analyze index t1_ind_id validate structure;

select name, lf_rows from index_stats;

NAME                              LF_ROWS

------------------------------ ----------

T1_IND_ID                               3

insert into t1 values(NULL,NULL);

commit;

analyze index t1_ind_id validate structure;

select name, lf_rows from index_stats;

NAME                              LF_ROWS

------------------------------ ----------

T1_IND_ID                               3

发现了什么?当id为NULL,data不是NULL或者id不是NULL,data为NULL的时候,索引条目都会增加,但当id,data都为NULL的时候,索引条目并没有增加。

treedump索引层次结构,可以清楚的发现复合索引的键值可以部分为NULL。

row#0[8018] flag: ------, lock: 0, len=14

col 0; len 2; (2):  c1 02

col 1; len 1; (1):  61

col 2; len 6; (6):  01 00 28 2c 00 00

row#1[7993] flag: ------, lock: 2, len=13

col 0; len 2; (2):  c1 04

col 1; NULL

col 2; len 6; (6):  01 00 28 2c 00 02

row#2[8006] flag: ------, lock: 0, len=12

col 0; NULL

col 1; len 1; (1):  62

col 2; len 6; (6):  01 00 28 2c 00 01

----- end of leaf block dump -----

通过复合索引的实验,更加清楚的说明了B树索引可以存储NULL值,但不允许索引字段全部为NULL,若索引字段全部为NULL,索引不会存储。了解了上述内容后,对我们实际工作有何意义?创建索引时应该考虑什么?

1.若列对应的业务数据确实不存在NULL值的情况,建议创建表语句添加NOT NULL的约束

2.列对应的业务数据可能存在NULL值的情况,可以通过如下方法创建索引:

a)函数索引

create index t_ind_id on t(nvl(id,0));

b)伪列

create index t_ind_id on t(id,0);

c)复合索引

create index t_ind_id on t(id,data);

延伸:

明白了索引不能存储全部NULL值的键值,就解释了某些执行计划走全表扫描的原因了。

drop table t purge;

create table t (id number,data varchar2(100));

begin

for i in 1..10000 loop

insert into t values(trunc(dbms_random.value(1,1000)),dbms_random.string('a',5));

end loop;

commit;

end loop;

create index t_id on t(id);

exec dbms_stats.gather_table_stats(USER,'T',CASCADE=>TRUE);

SQL> SELECT count(*) FROM t;

COUNT(*)

----------

10000

Execution Plan

----------------------------------------------------------

Plan hash value: 2966233522

-------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |

-------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      |     1 |     9   (0)| 00:00:01 |

|   1 |  SORT AGGREGATE    |      |     1 |            |          |

|   2 |   TABLE ACCESS FULL| T    | 10000 |     9   (0)| 00:00:01 |

-------------------------------------------------------------------

为什么执行计划是TABLE ACCESS FULL,因为字段id没有NOT NULL限制,如果走索引可能会漏掉id=NULL值的记录,从而统计结果有问题。

alter table t modify id not null;

SQL> SELECT count(*) FROM t;

COUNT(*)

----------

10000

Execution Plan

----------------------------------------------------------

Plan hash value: 3689807224

----------------------------------------------------------------------

| Id  | Operation             | Name | Rows  | Cost (%CPU)| Time     |

----------------------------------------------------------------------

|   0 | SELECT STATEMENT      |      |     1 |     7   (0)| 00:00:01 |

|   1 |  SORT AGGREGATE       |      |     1 |            |          |

|   2 |   INDEX FAST FULL SCAN| T_ID | 10000 |     7   (0)| 00:00:01 |

----------------------------------------------------------------------

可以看到执行计划从TABLE ACCESS FULL转换成了INDEX FAST FULL SCAN,优化了SQL的执行效率。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值