NULL 值與索引

lhrbest發表於2017-06-28

 NULL 值與索引



    NULL值是關聯式資料庫系統布林型(true,false,unknown)中比較特殊型別的一種值,通常稱為UNKNOWN或空值,即是未知的,不確定的。由於
NULL存在著無數的可能,因此NULL值也不等於NULL值,所以與NULL值相關的操作同樣都為NULL值。正是基於這樣一個特性,對於NULL值列上的B
樹索引導致了is null/is not null不走索引的情形,下面描述了NULL值與索引以及索引NULL列上的執行計劃,如何使得NULL值走索引的情形。
注:本文僅僅討論的是B樹索引上的NULL值,點陣圖索引不在此範圍之內。

一、null值與索引的關係

[sql] view plain copy
 print?
  1. scott@ORCL> create table t1(id number,val varchar2(1));  
  2.   
  3. -->為表t1建立唯一索引  
  4. scott@ORCL> create unique index i_t1_id on t1(id);  
  5.   
  6. scott@ORCL> insert into t1 select null,'Y' from dual;  
  7.   
  8. scott@ORCL> insert into t1 select null,'N' from dual;  
  9.   
  10. -->從上面的操作可知,儘管列id上存在唯一索引,但由於null值不等於任一null值,因此能夠成功插入  
  11. scott@ORCL> commit;  
  12.   
  13. -->再次為表新增唯一複合索引,即基於id列與val列  
  14. scott@ORCL> create unique index i_t1_id_val on t1(id,val);  
  15.   
  16. Index created.  
  17.   
  18. -->插入null,'N'的記錄時失敗,提示違反唯一性約束  
  19. scott@ORCL> insert into t1 select null,'N' from dual;  
  20. insert into t1 select null,'N' from dual  
  21. *  
  22. ERROR at line 1:  
  23. ORA-00001: unique constraint (SCOTT.I_T1_ID_VAL) violated  
  24.   
  25. -->插入null,'Y'的記錄時同樣失敗,提示違反唯一性約束  
  26. scott@ORCL> insert into t1 select null,'Y' from dual;  
  27. insert into t1 select null,'Y' from dual  
  28. *  
  29. ERROR at line 1:  
  30. ORA-00001: unique constraint (SCOTT.I_T1_ID_VAL) violated  
  31.   
  32. -->插入兩個null值成功  
  33. scott@ORCL> insert into t1 select null,null from dual;  
  34.   
  35. 1 row created.  
  36.   
  37. scott@ORCL> insert into t1 select null,null from dual;  
  38.   
  39. 1 row created.  
  40.   
  41. scott@ORCL> insert into t1 select null,'A' from dual;  
  42.   
  43. 1 row created.  
  44.   
  45. scott@ORCL> commit;  
  46.   
  47. Commit complete.  
  48.   
  49. scott@ORCL> set null unknown;  
  50. scott@ORCL> select * from t1;  
  51.   
  52.         ID VAL  
  53. ---------- ------------------------------  
  54. unknown    Y  
  55. unknown    N  
  56. unknown    unknown  
  57. unknown    unknown  
  58. unknown    A  
  59.   
  60. scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T1',cascade=>true);  
  61.            
  62. scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys  
  63.   2  from user_indexes  where table_name='T1';  
  64.   
  65. INDEX_NAME      INDEX_TYPE     BLEVEL LEAF_BLOCKS   NUM_ROWS STATUS   DISTINCT_KEYS  
  66. --------------- ---------- ---------- ----------- ---------- -------- -------------  
  67. I_T1_ID         NORMAL              0           0          0 VALID                0  
  68. I_T1_ID_VAL     NORMAL              0           1          3 VALID                3  
  69.   
  70. -->從上面的情形可知,  
  71. -->基於單列的唯一索引,可以多次插入null值,但其索引上並不儲存null值。  
  72. -->基於多列的複合索引,儘管全為null值的行可以多次插入,但不全為null的重複行則不能被插入(注,非唯一複合索引不存在此限制,此處不演示)。  
  73. -->基於多列的複合索引,對於全為null值的索引值也不會被儲存。如上面的情形,儘管插入了5條記錄,複合索引中只儲存了3條。  
  74. -->注:對於唯一性約束,null值不等於null值,同樣(null,null)也不等同於(null,null),所以上面的兩次null能夠被插入。  
二、null值與執行計劃
[sql] view plain copy
 print?
  1. scott@ORCL> set autot trace exp;  
  2. scott@ORCL> select * from t1 where id is null;  
  3.   
  4. Execution Plan  
  5. ----------------------------------------------------------  
  6. Plan hash value: 3617692013  
  7.   
  8. --------------------------------------------------------------------------  
  9. | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |  
  10. --------------------------------------------------------------------------  
  11. |   0 | SELECT STATEMENT  |      |     5 |     5 |     3   (0)| 00:00:01 |  
  12. |*  1 |  TABLE ACCESS FULL| T1   |     5 |     5 |     3   (0)| 00:00:01 |  
  13. --------------------------------------------------------------------------  
  14.   
  15. Predicate Information (identified by operation id):  
  16. ---------------------------------------------------  
  17.   
  18.    1 - filter("ID" IS NULL)  
  19.   
  20. -->從上面的測試可知,由於null值是不被儲存的,因此當使用id is null作為謂詞時,走了全表掃描  
  21.      
  22. scott@ORCL> select * from t1 where id is not null;  
  23.   
  24. Execution Plan  
  25. ----------------------------------------------------------  
  26. Plan hash value: 796913935  
  27.   
  28. ---------------------------------------------------------------------------------------  
  29. | Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |  
  30. ---------------------------------------------------------------------------------------  
  31. |   0 | SELECT STATEMENT            |         |     1 |     1 |     0   (0)| 00:00:01 |  
  32. |   1 |  TABLE ACCESS BY INDEX ROWID| T1      |     1 |     1 |     0   (0)| 00:00:01 |  
  33. |*  2 |   INDEX FULL SCAN           | I_T1_ID |     1 |       |     0   (0)| 00:00:01 |  
  34. ---------------------------------------------------------------------------------------  
  35.   
  36. Predicate Information (identified by operation id):  
  37. ---------------------------------------------------  
  38.   
  39.    2 - filter("ID" IS NOT NULL)  
  40.   
  41. -->從上面的測試可知,儘管當前表上id列上的所有值都為null,但不排除後續記錄插入的id不為null的列。  
  42. -->故當使用id is not null作為謂詞時,此時執行計劃中走了索引全掃描。     
  43.   
  44. -->下面來看看複合索引的情形     
  45. scott@ORCL> select * from t1 where val is null;  
  46.   
  47. Execution Plan  
  48. ----------------------------------------------------------  
  49. Plan hash value: 3617692013  
  50.   
  51. --------------------------------------------------------------------------  
  52. | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |  
  53. --------------------------------------------------------------------------  
  54. |   0 | SELECT STATEMENT  |      |     2 |     2 |     3   (0)| 00:00:01 |  
  55. |*  1 |  TABLE ACCESS FULL| T1   |     2 |     2 |     3   (0)| 00:00:01 |  
  56. --------------------------------------------------------------------------  
  57.   
  58. Predicate Information (identified by operation id):  
  59. ---------------------------------------------------  
  60.   
  61.    1 - filter("VAL" IS NULL)  
  62.   
  63. scott@ORCL> select * from t1 where val is not null;  
  64.   
  65. Execution Plan  
  66. ----------------------------------------------------------  
  67. Plan hash value: 1931510411  
  68.   
  69. --------------------------------------------------------------------------------  
  70. | Id  | Operation        | Name        | Rows  | Bytes | Cost (%CPU)| Time     |  
  71. --------------------------------------------------------------------------------  
  72. |   0 | SELECT STATEMENT |             |     3 |     3 |     1   (0)| 00:00:01 |  
  73. |*  1 |  INDEX FULL SCAN | I_T1_ID_VAL |     3 |     3 |     1   (0)| 00:00:01 |  
  74. --------------------------------------------------------------------------------  
  75.   
  76. Predicate Information (identified by operation id):  
  77. ---------------------------------------------------  
  78.   
  79.    1 - filter("VAL" IS NOT NULL)  
  80.   
  81. -->對於複合唯一索引的情形,當使用單列且非前導列謂詞時,使用is null與 is not null等同於單列唯一索引的情形。  
  82. -->即原理也是一樣的,val is null走全表掃描而val is not null走索引。因為null值不會被儲存。  
  83.   
  84. -->下面看看兩個列都作為謂詞的情形     
  85. scott@ORCL> select * from t1 where id is null and val is not null;  
  86.   
  87. Execution Plan  
  88. ----------------------------------------------------------  
  89. Plan hash value: 1040510552  
  90.   
  91. --------------------------------------------------------------------------------  
  92. | Id  | Operation        | Name        | Rows  | Bytes | Cost (%CPU)| Time     |  
  93. --------------------------------------------------------------------------------  
  94. |   0 | SELECT STATEMENT |             |     3 |     3 |     1   (0)| 00:00:01 |  
  95. |*  1 |  INDEX RANGE SCAN| I_T1_ID_VAL |     3 |     3 |     1   (0)| 00:00:01 |  
  96. --------------------------------------------------------------------------------  
  97.   
  98. Predicate Information (identified by operation id):  
  99. ---------------------------------------------------  
  100.   
  101.    1 - access("ID" IS NULL)  
  102.        filter("VAL" IS NOT NULL)  
  103.   
  104. -->從上面的測試可知,儘管兩個謂詞列上都存在索引,一個為單列唯一索引,一個為複合唯一索引。Oracle 選擇了複合索引I_T1_ID_VAL。      
  105.   
  106. scott@ORCL> select * from t1 where id is not null and val is null;  
  107.   
  108. Execution Plan  
  109. ----------------------------------------------------------  
  110. Plan hash value: 796913935  
  111.   
  112. ---------------------------------------------------------------------------------------  
  113. | Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |  
  114. ---------------------------------------------------------------------------------------  
  115. |   0 | SELECT STATEMENT            |         |     1 |     1 |     0   (0)| 00:00:01 |  
  116. |*  1 |  TABLE ACCESS BY INDEX ROWID| T1      |     1 |     1 |     0   (0)| 00:00:01 |  
  117. |*  2 |   INDEX FULL SCAN           | I_T1_ID |     1 |       |     0   (0)| 00:00:01 |  
  118. ---------------------------------------------------------------------------------------  
  119.   
  120. Predicate Information (identified by operation id):  
  121. ---------------------------------------------------  
  122.   
  123.    1 - filter("VAL" IS NULL)  
  124.    2 - filter("ID" IS NOT NULL)      
  125.   
  126. -->同樣的情形,謂詞的順序與複合索引定義的順序一樣,只不過第一個謂詞為id is not null,而第二個謂詞為val is null。  
  127. -->此時Oracle 選擇了單列唯一索引I_T1_ID  
  128. -->看到此,不知道大家是否已明白,即哪個列為is not null,則會使用該列上的索引,原因還是那句話,索引不儲存null值。  
  129. -->對於顛倒id列與val列以及id,val列為null或not null的其他不同組合情形不再演示,其執行計劃類似。  
三、使用is null走索引的情形
[sql] view plain copy
 print?
  1. scott@ORCL> set autot off;  
  2. --刪除原有表上的null值記錄  
  3. scott@ORCL> delete from t1 where val not in('Y','N'or val is null;  
  4.   
  5. rows deleted.  
  6.   
  7. scott@ORCL> update t1 set id=1 where val='Y';  
  8.   
  9. 1 row updated.  
  10.   
  11. scott@ORCL> update t1 set id=2 where val='N';  
  12.   
  13. 1 row updated.  
  14.   
  15. scott@ORCL> commit;  
  16.   
  17. Commit complete.  
  18.   
  19. -->對原有記錄更新後的情形  
  20. scott@ORCL> select * from t1;  
  21.   
  22.         ID VAL  
  23. ---------- ------------------------------  
  24.          1 Y  
  25.          2 N  
  26.   
  27. scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T1',cascade=>true);  
  28.   
  29. PL/SQL procedure successfully completed.  
  30.   
  31. -->修改表列id使之具有not null約束的特性  
  32. scott@ORCL> alter table t1 modify(id not null);  
  33.   
  34. Table altered.  
  35.   
  36. scott@ORCL> set autot trace exp;  
  37. scott@ORCL> select * from t1 where id is null;  
  38.   
  39. Execution Plan  
  40. ----------------------------------------------------------  
  41. Plan hash value: 3160894736  
  42.   
  43. --------------------------------------------------------------------------------  
  44. | Id  | Operation        | Name        | Rows  | Bytes | Cost (%CPU)| Time     |  
  45. --------------------------------------------------------------------------------  
  46. |   0 | SELECT STATEMENT |             |     1 |     5 |     0   (0)|          |  
  47. |*  1 |  FILTER          |             |       |       |            |          |  
  48. |   2 |   INDEX FULL SCAN| I_T1_ID_VAL |     2 |    10 |     1   (0)| 00:00:01 |  
  49. --------------------------------------------------------------------------------  
  50.   
  51. Predicate Information (identified by operation id):  
  52. ---------------------------------------------------  
  53.   
  54.    1 - filter(NULL IS NOT NULL)  
  55.   
  56. -->從上面的執行計劃中可知,當表t1列id上具有not null 約束時,此時使用id is null選擇了索引範圍掃描  
  57.   
  58. -->下面來看看列val is null 的情形     
  59. scott@ORCL> select * from t1 where val is null;  
  60.   
  61. Execution Plan  
  62. ----------------------------------------------------------  
  63. Plan hash value: 48744011  
  64.   
  65. ------------------------------------------------------------------------------------  
  66. | Id  | Operation            | Name        | Rows  | Bytes | Cost (%CPU)| Time     |  
  67. ------------------------------------------------------------------------------------  
  68. |   0 | SELECT STATEMENT     |             |     1 |     5 |     2   (0)| 00:00:01 |  
  69. |*  1 |  INDEX FAST FULL SCAN| I_T1_ID_VAL |     1 |     5 |     2   (0)| 00:00:01 |  
  70. ------------------------------------------------------------------------------------  
  71.   
  72. Predicate Information (identified by operation id):  
  73. ---------------------------------------------------  
  74.   
  75.    1 - filter("VAL" IS NULL)  
  76.   
  77. -->儘管val列上允許null值存在,但由於列id上具有not null 約束,且id列與val列存在複合唯一索引,因此此時選擇了索引快速全掃描  
  78. -->其餘不同組合情形大致相同,不再演示  
  79.   
  80. -->為表t1新增一條val為null的記錄  
  81. scott@ORCL> insert into t1 select 3,null from dual;  
  82.   
  83. 1 row created.  
  84.   
  85. scott@ORCL> commit;  
  86.   
  87. Commit complete.  
  88.   
  89. scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T1',cascade=>true);  
  90.   
  91. PL/SQL procedure successfully completed.  
  92.   
  93. -->下面的查詢中可以看出儘管只有列id有not null約束,當所有的索引值都被儲存  
  94. scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys  
  95.   2  from user_indexes  where table_name='T1';  
  96.   
  97. INDEX_NAME      INDEX_TYPE     BLEVEL LEAF_BLOCKS   NUM_ROWS STATUS   DISTINCT_KEYS  
  98. --------------- ---------- ---------- ----------- ---------- -------- -------------  
  99. I_T1_ID         NORMAL              0           1          3 VALID                3  
  100. I_T1_ID_VAL     NORMAL              0           1          3 VALID                3  
  101.   
  102. -->Author : Robinson Cheng  
  103. -->Blog :   http://blog.csdn.net/robinson_0612  
四、總結
    無論是單列唯一索引或複合唯一索引,對於可以為null的列或複合null值,Oracle不會為其儲存索引值。
    故在基於單列建立B樹唯一索引或多列建立B樹複合唯一索引的情形下,
    當列上允許為null值時
        where子句使用了基於is null的情形,其執行計劃走全表掃描。
        where子句使用了基於is not null的情形,其執行計劃走索引掃描(索引範圍掃描或索引全掃描)。
    當列上不允許為null值時,存在非null約束
        where子句使用了基於is null的情行,其執行計劃走索引掃描。
        where子句使用了基於is not null的情形,其執行計劃也是走索引掃描。
    注:此在Oracle 10g R2(linux)下的情形,不同的最佳化器版本可能會有偏差。

    在NULL值與索引(一)中講述了null值與索引的一些基本情況。其主要的內容為,基於允許存在null值的索引列,其索引值不會被儲存;其次
是由於這個特性導致了我們在使用is null時索引失效的情形;最後則是描述的透過為null值列新增not null約束來使得is null走索引。儘管我
們可以透過新增not null來解決is null走索引,當現實中的情況是仍然很多列根本是無法確定的,而必須保持其null特性。對於此種情形該如
何解決呢?

一、透過基於函式的索引來使得is null使用索引
[sql] view plain copy
 print?
  1. -->演示環境  
  2. scott@ORCL> select * from v$version where rownum<2;  
  3.   
  4. BANNER  
  5. ----------------------------------------------------------------  
  6. Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod  
  7.   
  8. -->建立測試表t2  
  9. scott@ORCL> create table t2(obj_id,obj_name) as select object_id,object_name from dba_objects;  
  10.   
  11. Table created.  
  12.   
  13. -->演示表t2上不存在not null約束  
  14. scott@ORCL> desc t2  
  15.  Name                          Null?    Type  
  16.  ----------------------------- -------- --------------------  
  17.  OBJ_ID                                 NUMBER  
  18.  OBJ_NAME                               VARCHAR2(128)  
  19.   
  20. -->為表t2建立一個普通的B樹索引  
  21. scott@ORCL> create index i_t2_obj_id on t2(obj_id);  
  22.   
  23. Index created.  
  24.   
  25. -->將表t2列obj_id<=100的obj_id置空  
  26. -->注:在Oracle 10g中空字串等同於null值  
  27. scott@ORCL> update t2 set obj_id='' where obj_id<=100;  
  28.   
  29. 99 rows updated.  
  30.   
  31. -->下面的查詢亦表明在此時空字串等同於null值  
  32. scott@ORCL> set null unknown  
  33. scott@ORCL> select * from t2 where obj_id is null and rownum<3;  
  34.   
  35.     OBJ_ID OBJ_NAME  
  36. ---------- ------------------------------  
  37. unknown    ICOL$  
  38. unknown    I_USER1  
  39.   
  40. -->收集統計資訊  
  41. scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T2',cascade=>true);  
  42.   
  43. PL/SQL procedure successfully completed.  
  44.   
  45. -->基於null值上使用not null會使用索引掃描,等同於前面 null值與索引(一) 中的描述  
  46. scott@ORCL> select count(*) from t2 where obj_id is not null;  
  47.   
  48. Execution Plan  
  49. ----------------------------------------------------------  
  50. Plan hash value: 3840858596  
  51.   
  52. -------------------------------------------------------------------------------------  
  53. | Id  | Operation             | Name        | Rows  | Bytes | Cost (%CPU)| Time     |  
  54. -------------------------------------------------------------------------------------  
  55. |   0 | SELECT STATEMENT      |             |     1 |     5 |     7   (0)| 00:00:01 |  
  56. |   1 |  SORT AGGREGATE       |             |     1 |     5 |            |          |  
  57. |*  2 |   INDEX FAST FULL SCAN| I_T2_OBJ_ID | 11719 | 58595 |     7   (0)| 00:00:01 |  
  58. -------------------------------------------------------------------------------------  
  59.   
  60. Predicate Information (identified by operation id):  
  61. ---------------------------------------------------  
  62.   
  63.    2 - filter("OBJ_ID" IS NOT NULL)  
  64.   
  65. -->列obj_id is null走全表掃描  
  66. scott@ORCL> select count(*) from t2 where obj_id is null;  
  67.   
  68. Execution Plan  
  69. ----------------------------------------------------------  
  70. Plan hash value: 3321871023  
  71.   
  72. ---------------------------------------------------------------------------  
  73. | Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |  
  74. ---------------------------------------------------------------------------  
  75. |   0 | SELECT STATEMENT   |      |     1 |     5 |    13   (0)| 00:00:01 |  
  76. |   1 |  SORT AGGREGATE    |      |     1 |     5 |            |          |  
  77. |*  2 |   TABLE ACCESS FULL| T2   |     1 |     5 |    13   (0)| 00:00:01 |  
  78. ---------------------------------------------------------------------------  
  79.   
  80. Predicate Information (identified by operation id):  
  81. ---------------------------------------------------  
  82.   
  83.    2 - filter("OBJ_ID" IS NULL)  
  84.   
  85. -->建立基於函式的索引來使得is null走索引  
  86. -->下面使用了nvl函式來建立函式索引,即當obj_id為null值時,儲存-1     
  87. scott@ORCL> create index i_fn_t2_obj_id on t2(nvl(obj_id,-1));  
  88.   
  89. Index created.  
  90.   
  91. -->收集索引資訊  
  92. scott@ORCL> exec dbms_stats.gather_index_stats('SCOTT','I_FN_T2_OBJ_ID');  
  93.   
  94. PL/SQL procedure successfully completed.  
  95.   
  96. -->可以看到下面的執行計劃中剛剛建立的函式索引已經生效I_FN_T2_OBJ_ID  
  97. scott@ORCL> select count(*) from t2 where nvl(obj_id,-1) = -1;  
  98.   
  99. Execution Plan  
  100. ----------------------------------------------------------  
  101. Plan hash value: 3983750858  
  102.   
  103. ------------------------------------------------------------------------------------  
  104. | Id  | Operation         | Name           | Rows  | Bytes | Cost (%CPU)| Time     |  
  105. ------------------------------------------------------------------------------------  
  106. |   0 | SELECT STATEMENT  |                |     1 |     5 |     1   (0)| 00:00:01 |  
  107. |   1 |  SORT AGGREGATE   |                |     1 |     5 |            |          |  
  108. |*  2 |   INDEX RANGE SCAN| I_FN_T2_OBJ_ID |   100 |   500 |     1   (0)| 00:00:01 |  
  109. ------------------------------------------------------------------------------------  
  110.   
  111. Predicate Information (identified by operation id):  
  112. ---------------------------------------------------  
  113.   
  114.    2 - access(NVL("OBJ_ID",(-1))=(-1))  
二、使用偽列建立基於函式的索引來使得is null使用索引
[sql] view plain copy
 print?
  1. -->下面透過新增一個值為-1(可取任意值)的偽列來建立索引  
  2. scott@ORCL> create index i_new_t2_obj_id on t2(obj_id,-1);  
  3.   
  4. Index created.  
  5.   
  6. -->收集索引資訊  
  7. scott@ORCL> exec dbms_stats.gather_index_stats('SCOTT','I_NEW_T2_OBJ_ID');  
  8.   
  9. PL/SQL procedure successfully completed.     
  10.   
  11. -->從下面的查詢可以看出obj_id is null使用了剛剛建立的索引  
  12. scott@ORCL> select count(*) from t2 where obj_id is null;  
  13.   
  14. Execution Plan  
  15. ----------------------------------------------------------  
  16. Plan hash value: 801885198  
  17.   
  18. -------------------------------------------------------------------------------------  
  19. | Id  | Operation         | Name            | Rows  | Bytes | Cost (%CPU)| Time     |  
  20. -------------------------------------------------------------------------------------  
  21. |   0 | SELECT STATEMENT  |                 |     1 |     5 |     2   (0)| 00:00:01 |  
  22. |   1 |  SORT AGGREGATE   |                 |     1 |     5 |            |          |  
  23. |*  2 |   INDEX RANGE SCAN| I_NEW_T2_OBJ_ID |    99 |   495 |     2   (0)| 00:00:01 |  
  24. -------------------------------------------------------------------------------------  
  25.   
  26. Predicate Information (identified by operation id):  
  27. ---------------------------------------------------  
  28.   
  29.    2 - access("OBJ_ID" IS NULL)  
  30.   
  31. -->檢視剛剛建立的所有索引的相關統計資訊     
  32. scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys  
  33.   2  from user_indexes where table_name='T2';  
  34.   
  35. INDEX_NAME      INDEX_TYPE                         BLEVEL LEAF_BLOCKS   NUM_ROWS STATUS   DISTINCT_KEYS  
  36. --------------- ------------------------------ ---------- ----------- ---------- -------- -------------  
  37. I_FN_T2_OBJ_ID  FUNCTION-BASED NORMAL                   1          26      11719 VALID            11621  
  38. I_NEW_T2_OBJ_ID FUNCTION-BASED NORMAL                   1          32      11719 VALID            11621  
  39. I_T2_OBJ_ID     NORMAL                                  1          25      11620 VALID            11620  
  40.   
  41. -->從上面的結果可知:  
  42. -->普通的B索引(I_T2_OBJ_ID)使用的索引塊最小,因為null值沒有被儲存,NUM_ROWS與DISTINCT_KEYS即是佐證  
  43. -->使用NVL函式建立的索引I_FN_T2_OBJ_ID中如實的反應了null值,即11620 + null值 = 11621  
  44. -->使用偽列建立的索引依然屬於函式索引,其耗用的葉節點塊數最多,因為多出了一個值(-1)來儲存  
  45. -->儘管使用NVL建立的函式佔用的磁碟空間小於使用偽列建立的索引,當在書寫謂詞時需要帶上NVL函式,而偽列索引中謂詞直接使用is null。  
三、NULL值與索引衍生特性
[sql] view plain copy
 print?
  1. -->由前面的種種事例再次說明NULL值不會被儲存到索引中,因此基於這個特性可以使用decode函式來壓縮索引列。  
  2. -->在實際應用的多數情形中,如表上有列印狀態列is_printed通常為兩種情形,已列印或未列印,假定1表示已列印,而0表示未列印。  
  3. -->通常情況下90%以上的單據都處於已列印狀態,而僅有10%左右的處於未列印。而經常要使用的情形是查詢未列印的單據並重新列印。  
  4. -->基於上述情況,可以使用點陣圖索引來解決,但此處我們討論的是B樹索引,故不考慮該情形(或者說你使用了非企業版Oracle,不支援點陣圖索引)  
  5. -->此處對於這類情形我們可以使用decode函式來解決這個問題  
  6.   
  7. -->更新表上的列,使之obj_id為1的行佔絕大多數  
  8. scott@ORCL> update t2 set obj_id=1 where obj_id is not null;  
  9.   
  10. 11620 rows updated.  
  11.   
  12. -->更新表,使之obj_id為0的行佔少部分  
  13. scott@ORCL> update t2 set obj_id = 0 where obj_id is null;  
  14.   
  15. 99 rows updated.  
  16.   
  17. scott@ORCL> commit;  
  18.   
  19. -->收集統計資訊  
  20. scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T2',cascade=>true);  
  21.   
  22. PL/SQL procedure successfully completed.  
  23.   
  24. -->表t2上obj_id列的最終分佈  
  25. scott@ORCL> select obj_id,count(*) from t2 group by obj_id;  
  26.   
  27.     OBJ_ID   COUNT(*)  
  28. ---------- ----------  
  29.          1      11620  
  30.          0         99     
  31.   
  32. -->使用decode函式建立索引  
  33. -->注意此處decode的使用,當obj_id非0值時,其值被賦予為null值,由於該null值不會儲存到索引,因此大部分obj_id列值為1的不會被索引  
  34. scott@ORCL> create index i_fn2_t2_obj_id on t2(decode(obj_id,0,0,null));  
  35.   
  36. Index created.  
  37.   
  38. -->收集索引上的統計資訊  
  39. scott@ORCL> exec dbms_stats.gather_index_stats('SCOTT','I_FN2_T2_OBJ_ID');  
  40.   
  41. PL/SQL procedure successfully completed.  
  42.   
  43. -->檢視新索引的執行計劃  
  44. scott@ORCL> set autot trace exp;  
  45. scott@ORCL> select count(*) from t2 where decode(obj_id,0,0,null) = 0;  
  46.   
  47. Execution Plan  
  48. ----------------------------------------------------------  
  49. Plan hash value: 1461308992  
  50.   
  51. -------------------------------------------------------------------------------------  
  52. | Id  | Operation         | Name            | Rows  | Bytes | Cost (%CPU)| Time     |  
  53. -------------------------------------------------------------------------------------  
  54. |   0 | SELECT STATEMENT  |                 |     1 |     3 |     1   (0)| 00:00:01 |  
  55. |   1 |  SORT AGGREGATE   |                 |     1 |     3 |            |          |  
  56. |*  2 |   INDEX RANGE SCAN| I_FN2_T2_OBJ_ID |    98 |   294 |     1   (0)| 00:00:01 |  
  57. -------------------------------------------------------------------------------------  
  58.   
  59. Predicate Information (identified by operation id):  
  60. ---------------------------------------------------  
  61.   
  62.    2 - access(DECODE("OBJ_ID",0,0,NULL)=0)  
  63.   
  64. -->當直接使用obj_id = 0來查詢時使用的是普通的B樹索引  
  65. scott@ORCL> select count(*) from t2 where obj_id = 0;  
  66.   
  67. Execution Plan  
  68. ----------------------------------------------------------  
  69. Plan hash value: 1804118247  
  70.   
  71. ---------------------------------------------------------------------------------  
  72. | Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |  
  73. ---------------------------------------------------------------------------------  
  74. |   0 | SELECT STATEMENT  |             |     1 |     3 |     1   (0)| 00:00:01 |  
  75. |   1 |  SORT AGGREGATE   |             |     1 |     3 |            |          |  
  76. |*  2 |   INDEX RANGE SCAN| I_T2_OBJ_ID |    99 |   297 |     1   (0)| 00:00:01 |  
  77. ---------------------------------------------------------------------------------  
  78.   
  79. Predicate Information (identified by operation id):  
  80. ---------------------------------------------------  
  81.   
  82.    2 - access("OBJ_ID"=0)     
  83.   
  84. -->當使用obj_id = 1來查詢時走全表掃描,因為obj_id = 1佔據表90%以上,由CBO特性決定了走全表掃描     
  85. scott@ORCL> select * from t2 where obj_id = 1;  
  86.   
  87. Execution Plan  
  88. ----------------------------------------------------------  
  89. Plan hash value: 1513984157  
  90.   
  91. --------------------------------------------------------------------------  
  92. | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |  
  93. --------------------------------------------------------------------------  
  94. |   0 | SELECT STATEMENT  |      | 11620 |   249K|    14   (8)| 00:00:01 |  
  95. |*  1 |  TABLE ACCESS FULL| T2   | 11620 |   249K|    14   (8)| 00:00:01 |  
  96. --------------------------------------------------------------------------  
  97.   
  98. Predicate Information (identified by operation id):  
  99. ---------------------------------------------------  
  100.   
  101.    1 - filter("OBJ_ID"=1)  
  102.      
  103. -->表t2上所有索引的統計資訊  
  104. scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys  
  105.   2  from user_indexes where table_name='T2';  
  106.     
  107. INDEX_NAME      INDEX_TYPE                         BLEVEL LEAF_BLOCKS   NUM_ROWS STATUS   DISTINCT_KEYS  
  108. --------------- ------------------------------ ---------- ----------- ---------- -------- -------------  
  109. I_FN_T2_OBJ_ID  FUNCTION-BASED NORMAL                   1          40      11719 VALID                2  
  110. I_NEW_T2_OBJ_ID FUNCTION-BASED NORMAL                   1          52      11719 VALID                2  
  111. I_FN2_T2_OBJ_ID FUNCTION-BASED NORMAL                   0           1         99 VALID                1  
  112. I_T2_OBJ_ID     NORMAL                                  1          40      11719 VALID                2  
  113.   
  114. -->從上面的結果可知,索引I_FN2_T2_OBJ_ID僅僅儲存了99跳記錄,且DISTINCT_KEYS值為1個,因為所有非0值的全部被置NULL。  
  115. -->以上方法實現了索引壓縮,避免了較大索引維護所需的開銷,同時也提高了查詢效能。  
  116. -->Author : Robinson Cheng  
  117. -->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約束應節省磁碟空間。







About Me

...............................................................................................................................

● 本文轉載自http://blog.csdn.net/leshami/article/details/7438397,樂沙彌大師

● 本文在itpub(http://blog.itpub.net/26736162)、部落格園(http://www.cnblogs.com/lhrbest)和個人微信公眾號(xiaomaimiaolhr)上有同步更新

● 本文itpub地址:http://blog.itpub.net/26736162/abstract/1/

● 本文部落格園地址:http://www.cnblogs.com/lhrbest

● 本文pdf版及小麥苗雲盤地址:http://blog.itpub.net/26736162/viewspace-1624453/

● 資料庫筆試面試題庫及解答:http://blog.itpub.net/26736162/viewspace-2134706/

● QQ群:230161599     微信群:私聊

● 聯絡我請加QQ好友(646634621),註明新增緣由

● 於 2017-06-02 09:00 ~ 2017-06-30 22:00 在魔都完成

● 文章內容來源於小麥苗的學習筆記,部分整理自網路,若有侵權或不當之處還請諒解

● 版權所有,歡迎分享本文,轉載請保留出處

...............................................................................................................................

拿起手機使用微信客戶端掃描下邊的左邊圖片來關注小麥苗的微信公眾號:xiaomaimiaolhr,掃描右邊的二維碼加入小麥苗的QQ群,學習最實用的資料庫技術。

NULL 值與索引
DBA筆試面試講解
歡迎與我聯絡

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26736162/viewspace-2141337/,如需轉載,請註明出處,否則將追究法律責任。

相關文章