Oracle is null不走索引问题

由于业务的需要:查看某列值为NULL的记录有多少。这是一个十分简单的需求,同时也很容易实现。无非就是使用如下语句:
  1. SELECT * FROM table1 WHERE xx IS NULL;  
结果得出的结果只有几十条记录,但是执行的时间比我想象中的长了许多。一般这样的查询只是要秒秒钟的问题,但是却花了十几秒,而平时使用IS NOT NULL也没有那么慢啊。于是在有空的时候就查看原因。
以下是一个模拟(附带着我的思路):

构造测试表

  1. DROP TABLE t PURGE;  
  2. CREATE TABLE t(a NUMBER);  
  3. CREATE INDEX idx_t ON t(a);  
  4. INSERT INTO t VALUES(NULL);  
  5. INSERT INTO t VALUES(NULL);  
  6. INSERT INTO t VALUES(1);  
  7. INSERT INTO t VALUES(1);  
  8. INSERT INTO t VALUES(1);  
  9. INSERT INTO t VALUES(1);  
  10. INSERT INTO t VALUES(1);  
  11. INSERT INTO t VALUES(1);  
  12. INSERT INTO t VALUES(1);  
  13. COMMIT;  
  14. EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname=>USER, tabname=>'T');  
  15. EXEC DBMS_STATS.GATHER_INDEX_STATS(ownname=>USER, indname=>'IDX_T');  
遇到这样的问题我首先是看一下执行语句的相关的执行计划
  1. SET NULL UNKOWN --设置如果只为NULL就以UNKOWN显示  
  2. SET AUTOTRACE ON EXPLAIN  
  3. SELECT * FROM t WHERE a IS NULL;  
  4.          A  
  5. ----------  
  6. UNKNOWN  
  7. UNKNOWN  
  8.   
  9. Execution Plan  
  10. ----------------------------------------------------------  
  11. Plan hash value: 1601196873  
  12.   
  13. --------------------------------------------------------------------------  
  14. | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |  
  15. --------------------------------------------------------------------------  
  16. |   0 | SELECT STATEMENT  |      |     2 |     6 |     3   (0)| 00:00:01 |  
  17. |*  1 |  TABLE ACCESS FULL| T    |     2 |     6 |     3   (0)| 00:00:01 |  
  18. --------------------------------------------------------------------------  
  19.   
  20. Predicate Information (identified by operation id):  
  21. ---------------------------------------------------  
  22.   
  23.    1 - filter("A" IS NULL)  
  24.   
  25. SELECT * FROM t WHERE a IS NOT NULL;     
  26.          A  
  27. ----------  
  28.          1  
  29.          1  
  30.          1  
  31.          1  
  32.          1  
  33.          1  
  34.          1  
  35.   
  36. rows selected.  
  37.   
  38.   
  39. Execution Plan  
  40. ----------------------------------------------------------  
  41. Plan hash value: 1512349780  
  42.   
  43. --------------------------------------------------------------------------  
  44. | Id  | Operation        | Name  | Rows  | Bytes | Cost (%CPU)| Time     |  
  45. --------------------------------------------------------------------------  
  46. |   0 | SELECT STATEMENT |       |     7 |    21 |     1   (0)| 00:00:01 |  
  47. |*  1 |  INDEX FULL SCAN | IDX_T |     7 |    21 |     1   (0)| 00:00:01 |  
  48. --------------------------------------------------------------------------  
  49.   
  50. Predicate Information (identified by operation id):  
  51. ---------------------------------------------------  
  52.   
  53.    1 - filter("A" IS NOT NULL)   
从上的现象看出使用IS NULL时候没有使用索引,而使用IS NOT NULL有使用索引,这让我很郁闷。于是我顺道的就查看了一下IDX_T索引的所在的列。

如下:

  1. SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME   
  2. FROM USER_IND_COLUMNS   
  3. WHERE INDEX_NAME = 'IDX_T';  
  4.   
  5. TABLE_NAME                     INDEX_NAME                     COLUMN_NAME  
  6. ------------------------------ ------------------------------ --------------------  
  7. T                              IDX_T                          A  
很明显的展示了在表T的a列上是有索引的,这时我就在IS NULL和IS NOT NULL中迷茫了,不知所措啊。于是只能将问题放在一边了。直到一天我知道了DBMS_STATS.GATHER_INDEX_STATS()的作用的时候我又回想起了这个情况于是我有查看了一下有关索引的统计信息。

如下:

  1. SELECT index_name, num_rows from user_indexes where index_name = 'IDX_T';  
  2. INDEX_NAME                       NUM_ROWS  
  3. ------------------------------ ----------  
  4. IDX_T                                   7  
从上面可以看出索引的统计信息的NUM_ROWS字段只有7行,而实际的数据总量是9行。按道理应该索引的NUM_ROWS应该是9才对缺少了2,很明显少了值为NULL的那2行。原来索引是不将NULL值的行记录在索引中的。在网上查了一下视乎有这种说法(本人不太确定只是猜测,毕竟统计信息的知识太多个人感觉好难,也容易混淆,难记)。
知道了情况就问呗,问如何解决这样的情况,最后得到了答案。创建一个伪列的索引就行了。
具体如下:
  1. SET AUTOTRACE OFF  
  2. DROP INDEX idx_t;  
  3. CREATE INDEX idx_t ON t(a, 0);  
  4. EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname=>USER, tabname=>'T');  
  5. EXEC DBMS_STATS.GATHER_INDEX_STATS(ownname=>USER, indname=>'IDX_T');  
  6. SET AUTOTRACE ON EXPLAIN  
  7. SELECT * FROM t WHERE a IS NULL;  
  8.          A  
  9. ----------  
  10. UNKNOWN  
  11. UNKNOWN  
  12.   
  13.   
  14. Execution Plan  
  15. ----------------------------------------------------------  
  16. Plan hash value: 2296882198  
  17.   
  18. --------------------------------------------------------------------------  
  19. | Id  | Operation        | Name  | Rows  | Bytes | Cost (%CPU)| Time     |  
  20. --------------------------------------------------------------------------  
  21. |   0 | SELECT STATEMENT |       |     2 |     6 |     1   (0)| 00:00:01 |  
  22. |*  1 |  INDEX RANGE SCAN| IDX_T |     2 |     6 |     1   (0)| 00:00:01 |  
  23. --------------------------------------------------------------------------  
  24.   
  25. Predicate Information (identified by operation id):  
  26. ---------------------------------------------------  
  27.   
  28.    1 - access("A" IS NULL)  
  29.   
  30.   
  31. SELECT * FROM t WHERE a IS NOT NULL;   
  32.          A  
  33. ----------  
  34.          1  
  35.          1  
  36.          1  
  37.          1  
  38.          1  
  39.          1  
  40.          1  
  41.   
  42. rows selected.  
  43.   
  44.   
  45. Execution Plan  
  46. ----------------------------------------------------------  
  47. Plan hash value: 1512349780  
  48.   
  49. --------------------------------------------------------------------------  
  50. | Id  | Operation        | Name  | Rows  | Bytes | Cost (%CPU)| Time     |  
  51. --------------------------------------------------------------------------  
  52. |   0 | SELECT STATEMENT |       |     7 |    21 |     1   (0)| 00:00:01 |  
  53. |*  1 |  INDEX FULL SCAN | IDX_T |     7 |    21 |     1   (0)| 00:00:01 |  
  54. --------------------------------------------------------------------------  
  55.   
  56. Predicate Information (identified by operation id):  
  57. ---------------------------------------------------  
  58.   
  59.    1 - filter("A" IS NOT NULL)  
从上面可以上到IS NULL 走索引了,IS NOT NULL 也走索引
  1. SELECT index_name, num_rows from user_indexes where index_name = 'IDX_T';  
  2.   
  3. INDEX_NAME                       NUM_ROWS  
  4. ------------------------------ ----------  
  5. IDX_T                                   9  
索引的统计信息是9和表的行数是一样的。

在接触到函数索引的时候也有提到以上类似的场景(查询NULL值的记录,NULL值的记录在表中不到10%)。就能使用函数索引来加快搜索。

如下:

  1. SET AUTOTRACE OFF  
  2. DROP INDEX idx_t;  
  3. CREATE IDNEX idx_t ON t(DECODE(a, NULL,0, NULL));  
  4. EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname=>USER, tabname=>'T');  
  5. EXEC DBMS_STATS.GATHER_INDEX_STATS(ownname=>USER, indname=>'IDX_T');  
  6. SET AUTOTRACE ON EXPLAIN  
  7.   
  8. SELECT * FROM t WHERE DECODE(a, NULL, 0, NULL) = 0;  
  9.          A  
  10. ----------  
  11. UNKNOWN  
  12. UNKNOWN  
  13.   
  14. Execution Plan  
  15. ----------------------------------------------------------  
  16. Plan hash value: 1594971208  
  17.   
  18. -------------------------------------------------------------------------------------  
  19. | Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |  
  20. -------------------------------------------------------------------------------------  
  21. |   0 | SELECT STATEMENT            |       |     2 |     8 |     2   (0)| 00:00:01 |  
  22. |   1 |  TABLE ACCESS BY INDEX ROWID| T     |     2 |     8 |     2   (0)| 00:00:01 |  
  23. |*  2 |   INDEX RANGE SCAN          | IDX_T |     2 |       |     1   (0)| 00:00:01 |  
  24. -------------------------------------------------------------------------------------  
  25.   
  26. Predicate Information (identified by operation id):  
  27. ---------------------------------------------------  
  28.   
  29.    2 - access(DECODE(TO_CHAR("A"),NULL,0,NULL)=0)  
  30.   
  31. SELECT index_name, num_rows from user_indexes where index_name = 'IDX_T';  
  32.   
  33. INDEX_NAME                       NUM_ROWS  
  34. ------------------------------ ----------  
  35. IDX_T                                   2  
可以看到以上结果走了索引,而且统计信息只有2条。在这里我做一下解释(是个人理解的,不对请指正)我们在创建索引的时候是使用DECODE(a, NULL, 0, NULL)。这个函数很好理解就是使用a的值和NULL比较,如果相等就变成0,而如果不相等就变成NULL。在表的记录中a的值只有2行为NULL,7行是有真实值,所以在创建索引时就生成2个0值,和7个NULL值。而根据前面所的oracle不会将NULL值行加入索引中,所以只能将为0的2行加入索引中了。所以索引统计信息的NUM_ROWS为2。 注意:要让oracle使用函数索引在谓词(WHERE)后要有相关函数。如上的SELECT查询。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于索引列,一般情况下是不能存储Null值的。这是因为建立索引时需要对列的值进行比较操作,并将值进行排序和存储在树结构中。Null值的特殊性在于它表示缺失或未知的值,与其他具体的值进行比较时会出现问题。因此,索引通常不包含Null值。 然而,需要注意的是,不同的数据库对于Null值的处理方式可能有所不同。例如,SQL Server的索引是包含Null值的,而Oracle索引则不包含Null值。因此,在使用is null条件时,SQL Server的索引仍然可以生效,而Oracle索引则会失效。 对于Oracle数据库,如果需要使用is null条件进行查询并希望索引生效,可以考虑使用复合索引的方式。通过将is null条件所在的列和其他相关列一起建立索引,就可以使得该查询能够使用索引进行优化。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [MySQL索引失效的几种情况汇总](https://download.csdn.net/download/weixin_38659248/13682560)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [SQL SERVER 中is null 和 is not null 将会导致索引失效吗?](https://blog.csdn.net/weixin_33698043/article/details/90652951)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [通过复合索引来避免is null使索引失效!](https://blog.csdn.net/zq9017197/article/details/7089332)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值