NULL 值与索引(二)

    在NULL值与索引(一)中讲述了null值与索引的一些基本情况。其主要的内容为,基于允许存在null值的索引列,其索引值不会被存储;其次 是由于这个特性导致了我们在使用is null时索引失效的情形;最后则是描述的通过为null值列添加not null约束来使得is null走索引。尽管我 们可以通过添加not null来解决is null走索引,当现实中的情况是仍然很多列根本是无法确定的,而必须保持其null特性。对于此种情形该如 何解决呢? 一、通过基于函数的索引来使得is null使用索引

-->演示环境
scott@ORCL> select * from v$version where rownum<2;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod

-->创建测试表t2
scott@ORCL> create table t2(obj_id,obj_name) as select object_id,object_name from dba_objects;

Table created.

-->演示表t2上不存在not null约束
scott@ORCL> desc t2
 Name                          Null?    Type
 ----------------------------- -------- --------------------
 OBJ_ID                                 NUMBER
 OBJ_NAME                               VARCHAR2(128)

-->为表t2创建一个普通的B树索引
scott@ORCL> create index i_t2_obj_id on t2(obj_id);

Index created.

-->将表t2列obj_id<=100的obj_id置空
-->注:在Oracle 10g中空字符串等同于null值
scott@ORCL> update t2 set obj_id='' where obj_id<=100;

99 rows updated.

-->下面的查询亦表明在此时空字符串等同于null值
scott@ORCL> set null unknown
scott@ORCL> select * from t2 where obj_id is null and rownum<3;

    OBJ_ID OBJ_NAME
---------- ------------------------------
unknown    ICOL$
unknown    I_USER1

-->收集统计信息
scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T2',cascade=>true);

PL/SQL procedure successfully completed.

-->基于null值上使用not null会使用索引扫描,等同于前面 null值与索引(一) 中的描述
scott@ORCL> select count(*) from t2 where obj_id is not null;

Execution Plan
----------------------------------------------------------
Plan hash value: 3840858596

-------------------------------------------------------------------------------------
| Id  | Operation             | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |             |     1 |     5 |     7   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE       |             |     1 |     5 |            |          |
|*  2 |   INDEX FAST FULL SCAN| I_T2_OBJ_ID | 11719 | 58595 |     7   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("OBJ_ID" IS NOT NULL)

-->列obj_id is null走全表扫描
scott@ORCL> select count(*) from t2 where obj_id is null;

Execution Plan
----------------------------------------------------------
Plan hash value: 3321871023

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     5 |    13   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |     5 |            |          |
|*  2 |   TABLE ACCESS FULL| T2   |     1 |     5 |    13   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("OBJ_ID" IS NULL)

-->创建基于函数的索引来使得is null走索引
-->下面使用了nvl函数来创建函数索引,即当obj_id为null值时,存储-1   
scott@ORCL> create index i_fn_t2_obj_id on t2(nvl(obj_id,-1));

Index created.

-->收集索引信息
scott@ORCL> exec dbms_stats.gather_index_stats('SCOTT','I_FN_T2_OBJ_ID');

PL/SQL procedure successfully completed.

-->可以看到下面的执行计划中刚刚创建的函数索引已经生效I_FN_T2_OBJ_ID
scott@ORCL> select count(*) from t2 where nvl(obj_id,-1) = -1;

Execution Plan
----------------------------------------------------------
Plan hash value: 3983750858

------------------------------------------------------------------------------------
| Id  | Operation         | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                |     1 |     5 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE   |                |     1 |     5 |            |          |
|*  2 |   INDEX RANGE SCAN| I_FN_T2_OBJ_ID |   100 |   500 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access(NVL("OBJ_ID",(-1))=(-1))

二、使用伪列创建基于函数的索引来使得is null使用索引

-->下面通过添加一个值为-1(可取任意值)的伪列来创建索引
scott@ORCL> create index i_new_t2_obj_id on t2(obj_id,-1);

Index created.

-->收集索引信息
scott@ORCL> exec dbms_stats.gather_index_stats('SCOTT','I_NEW_T2_OBJ_ID');

PL/SQL procedure successfully completed.   

-->从下面的查询可以看出obj_id is null使用了刚刚创建的索引
scott@ORCL> select count(*) from t2 where obj_id is null;

Execution Plan
----------------------------------------------------------
Plan hash value: 801885198

-------------------------------------------------------------------------------------
| Id  | Operation         | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                 |     1 |     5 |     2   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE   |                 |     1 |     5 |            |          |
|*  2 |   INDEX RANGE SCAN| I_NEW_T2_OBJ_ID |    99 |   495 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJ_ID" IS NULL)

-->查看刚刚创建的所有索引的相关统计信息   
scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys
  2  from user_indexes where table_name='T2';

INDEX_NAME      INDEX_TYPE                         BLEVEL LEAF_BLOCKS   NUM_ROWS STATUS   DISTINCT_KEYS
--------------- ------------------------------ ---------- ----------- ---------- -------- -------------
I_FN_T2_OBJ_ID  FUNCTION-BASED NORMAL                   1          26      11719 VALID            11621
I_NEW_T2_OBJ_ID FUNCTION-BASED NORMAL                   1          32      11719 VALID            11621
I_T2_OBJ_ID     NORMAL                                  1          25      11620 VALID            11620

-->从上面的结果可知:
-->普通的B索引(I_T2_OBJ_ID)使用的索引块最小,因为null值没有被存储,NUM_ROWS与DISTINCT_KEYS即是佐证
-->使用NVL函数创建的索引I_FN_T2_OBJ_ID中如实的反应了null值,即11620 + null值 = 11621
-->使用伪列创建的索引依然属于函数索引,其耗用的叶节点块数最多,因为多出了一个值(-1)来存储
-->尽管使用NVL创建的函数占用的磁盘空间小于使用伪列创建的索引,当在书写谓词时需要带上NVL函数,而伪列索引中谓词直接使用is null。

三、NULL值与索引衍生特性

-->由前面的种种事例再次说明NULL值不会被存储到索引中,因此基于这个特性可以使用decode函数来压缩索引列。
-->在实际应用的多数情形中,如表上有打印状态列is_printed通常为两种情形,已打印或未打印,假定1表示已打印,而0表示未打印。
-->通常情况下90%以上的单据都处于已打印状态,而仅有10%左右的处于未打印。而经常要使用的情形是查询未打印的单据并重新打印。
-->基于上述情况,可以使用位图索引来解决,但此处我们讨论的是B树索引,故不考虑该情形(或者说你使用了非企业版Oracle,不支持位图索引)
-->此处对于这类情形我们可以使用decode函数来解决这个问题

-->更新表上的列,使之obj_id为1的行占绝大多数
scott@ORCL> update t2 set obj_id=1 where obj_id is not null;

11620 rows updated.

-->更新表,使之obj_id为0的行占少部分
scott@ORCL> update t2 set obj_id = 0 where obj_id is null;

99 rows updated.

scott@ORCL> commit;

-->收集统计信息
scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T2',cascade=>true);

PL/SQL procedure successfully completed.

-->表t2上obj_id列的最终分布
scott@ORCL> select obj_id,count(*) from t2 group by obj_id;

    OBJ_ID   COUNT(*)
---------- ----------
         1      11620
         0         99   

-->使用decode函数创建索引
-->注意此处decode的使用,当obj_id非0值时,其值被赋予为null值,由于该null值不会存储到索引,因此大部分obj_id列值为1的不会被索引
scott@ORCL> create index i_fn2_t2_obj_id on t2(decode(obj_id,0,0,null));

Index created.

-->收集索引上的统计信息
scott@ORCL> exec dbms_stats.gather_index_stats('SCOTT','I_FN2_T2_OBJ_ID');

PL/SQL procedure successfully completed.

-->查看新索引的执行计划
scott@ORCL> set autot trace exp;
scott@ORCL> select count(*) from t2 where decode(obj_id,0,0,null) = 0;

Execution Plan
----------------------------------------------------------
Plan hash value: 1461308992

-------------------------------------------------------------------------------------
| Id  | Operation         | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                 |     1 |     3 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE   |                 |     1 |     3 |            |          |
|*  2 |   INDEX RANGE SCAN| I_FN2_T2_OBJ_ID |    98 |   294 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access(DECODE("OBJ_ID",0,0,NULL)=0)

-->当直接使用obj_id = 0来查询时使用的是普通的B树索引
scott@ORCL> select count(*) from t2 where obj_id = 0;

Execution Plan
----------------------------------------------------------
Plan hash value: 1804118247

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             |     1 |     3 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE   |             |     1 |     3 |            |          |
|*  2 |   INDEX RANGE SCAN| I_T2_OBJ_ID |    99 |   297 |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJ_ID"=0)   

-->当使用obj_id = 1来查询时走全表扫描,因为obj_id = 1占据表90%以上,由CBO特性决定了走全表扫描   
scott@ORCL> select * from t2 where obj_id = 1;

Execution Plan
----------------------------------------------------------
Plan hash value: 1513984157

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 11620 |   249K|    14   (8)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T2   | 11620 |   249K|    14   (8)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("OBJ_ID"=1)
   
-->表t2上所有索引的统计信息
scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys
  2  from user_indexes where table_name='T2';
  
INDEX_NAME      INDEX_TYPE                         BLEVEL LEAF_BLOCKS   NUM_ROWS STATUS   DISTINCT_KEYS
--------------- ------------------------------ ---------- ----------- ---------- -------- -------------
I_FN_T2_OBJ_ID  FUNCTION-BASED NORMAL                   1          40      11719 VALID                2
I_NEW_T2_OBJ_ID FUNCTION-BASED NORMAL                   1          52      11719 VALID                2
I_FN2_T2_OBJ_ID FUNCTION-BASED NORMAL                   0           1         99 VALID                1
I_T2_OBJ_ID     NORMAL                                  1          40      11719 VALID                2

-->从上面的结果可知,索引I_FN2_T2_OBJ_ID仅仅存储了99跳记录,且DISTINCT_KEYS值为1个,因为所有非0值的全部被置NULL。
-->以上方法实现了索引压缩,避免了较大索引维护所需的开销,同时也提高了查询性能。
-->Author : Robinson Cheng
-->Blog :   http://blog.csdn.net/robinson_0612

四、总结     1、对于用于连接或经常被谓词使用到的列应尽可能避免NULL值属性,因为它容易导致索引失效。     2、为需要使用NULL值的列添加缺省值(alter table tb modify(col default 'Y'))。     3、如果NULL值不可避免也不能使用缺省值,应考虑为该常用列使用nvl函数创建索引,或使用伪列来创建索引以提高查询性能。     4、对于复合索引应保证索引中至少有一列不为NULL值,还是因为全部列为NULL时不被索引存储,以保证使用is null是可以使用索引。     5、对于复合索引应保证索引列应使用数据类型长度最小的列来添加not null约束应节省磁盘空间。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏乐沙弥的世界

Oracle字符集与字符类型存储空间占用

902
来自专栏沃趣科技

语句效率统计视图 | 全方位认识 sys 系统库

在上一篇《统计信息查询视图|全方位认识 sys 系统库》中,我们介绍了利用sys 系统库的查询统计信息的快捷视图,本期将为大家介绍语句查询效率语句统计信息相关的...

2365
来自专栏数据和云

案例分析:倾斜值传入导致 SQL 资源消耗升高

作者 | 邓秋爽:云和恩墨技术工程师,有超过七年超大型数据库专业服务经验,擅长 Oracle 数据库优化、SQL 优化和 Troubleshooting。

1294
来自专栏乐沙弥的世界

PL/SQL --> INSTEAD OF 触发器

INSTEAD OF 触发器常用于管理编写不可更新的视图,INSTEAD-OF触发器必须是行级的。

812
来自专栏乐沙弥的世界

dbms_xplan之display_cursor函数的使用

        DBMS_XPLAN包中display_cursor函数不同于display函数,display_curso...

1893
来自专栏个人分享

Spark工程开发常用函数与方法(Scala语言)

import org.apache.spark.{SparkContext, SparkConf} import org.apache.spark.sql.{S...

1592
来自专栏乐沙弥的世界

NULL 值与索引(一)

    NULL值是关系数据库系统布尔型(true,false,unknown)中比较特殊类型的一种值,通常称为UNKNOWN或空值,即是未知的,不确定的。由...

3022
来自专栏向治洪

百度地图之收索视野内的建筑物

根据用户移动地图的位置,显示在视野范围内的建筑物,简单的思路是,添加地图监听,当地图移动结束之后,计算出当前屏幕四个角的GeoPoint,根据这4个点,通过my...

1789
来自专栏java达人

mysql left( right ) join使用on 与where 筛选的差异

有这样的一个问题mysql查询使用mysql中left(right)join筛选条件在on与where查询出的数据是否有差异。 可能只看着两个关键字看不出任...

2347
来自专栏企鹅号快讯

数据库中间件 Sharding-JDBC 源码分析——SQL 解析之插入SQL

1. 概述 本文前置阅读: 《SQL 解析(一)之词法解析》 《SQL 解析(二)之SQL解析》 本文分享插入SQL解析的源码实现。 不考虑 INSERT SE...

2005

扫码关注云+社区

领取腾讯云代金券