NULL存在着無數的可能,因此NULL值也不等於NULL值,所以與NULL值相關的操作同樣都爲NULL值。正是基於這樣一個特性,對於NULL值列上的B
樹索引導致了is null/is not null不走索引的情形,下面描述了NULL值與索引以及索引NULL列上的執行計劃,如何使得NULL值走索引的情形。
注:本文僅僅討論的是B樹索引上的NULL值,位圖索引不在此範圍之內。
一、null值與索引的關係
- scott@ORCL> create table t1(id number,val varchar2(1));
- –>爲表t1創建唯一索引
- scott@ORCL> create unique index i_t1_id on t1(id);
- scott@ORCL> insert into t1 select null,‘Y’ from dual;
- scott@ORCL> insert into t1 select null,‘N’ from dual;
- –>從上面的操作可知,儘管列id上存在唯一索引,但由於null值不等於任一null值,因此能夠成功插入
- scott@ORCL> commit;
- –>再次爲表添加唯一複合索引,即基於id列與val列
- scott@ORCL> create unique index i_t1_id_val on t1(id,val);
- Index created.
- –>插入null,’N’的記錄時失敗,提示違反唯一性約束
- scott@ORCL> insert into t1 select null,‘N’ from dual;
- insert into t1 select null,‘N’ from dual
- *
- ERROR at line 1:
- ORA-00001: unique constraint (SCOTT.I_T1_ID_VAL) violated
- –>插入null,’Y’的記錄時同樣失敗,提示違反唯一性約束
- scott@ORCL> insert into t1 select null,‘Y’ from dual;
- insert into t1 select null,‘Y’ from dual
- *
- ERROR at line 1:
- ORA-00001: unique constraint (SCOTT.I_T1_ID_VAL) violated
- –>插入兩個null值成功
- scott@ORCL> insert into t1 select null,null from dual;
- 1 row created.
- scott@ORCL> insert into t1 select null,null from dual;
- 1 row created.
- scott@ORCL> insert into t1 select null,‘A’ from dual;
- 1 row created.
- scott@ORCL> commit;
- Commit complete.
- scott@ORCL> set null unknown;
- scott@ORCL> select * from t1;
- ID VAL
- ———- ——————————
- unknown Y
- unknown N
- unknown unknown
- unknown unknown
- unknown A
- scott@ORCL> exec dbms_stats.gather_table_stats(‘SCOTT’,‘T1’,cascade=>true);
- scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys
- 2 from user_indexes where table_name=‘T1’;
- INDEX_NAME INDEX_TYPE BLEVEL LEAF_BLOCKS NUM_ROWS STATUS DISTINCT_KEYS
- ————— ———- ———- ———– ———- ——– ————-
- I_T1_ID NORMAL 0 0 0 VALID 0
- I_T1_ID_VAL NORMAL 0 1 3 VALID 3
- –>從上面的情形可知,
- –>基於單列的唯一索引,可以多次插入null值,但其索引上並不存儲null值。
- –>基於多列的複合索引,儘管全爲null值的行可以多次插入,但不全爲null的重複行則不能被插入(注,非唯一複合索引不存在此限制,此處不演示)。
- –>基於多列的複合索引,對於全爲null值的索引值也不會被存儲。如上面的情形,儘管插入了5條記錄,複合索引中只存儲了3條。
- –>注:對於唯一性約束,null值不等於null值,同樣(null,null)也不等同於(null,null),所以上面的兩次null能夠被插入。
scott@ORCL> create table t1(id number,val varchar2(1));
-->爲表t1創建唯一索引
scott@ORCL> create unique index i_t1_id on t1(id);
scott@ORCL> insert into t1 select null,'Y' from dual;
scott@ORCL> insert into t1 select null,'N' from dual;
-->從上面的操作可知,儘管列id上存在唯一索引,但由於null值不等於任一null值,因此能夠成功插入
scott@ORCL> commit;
-->再次爲表添加唯一複合索引,即基於id列與val列
scott@ORCL> create unique index i_t1_id_val on t1(id,val);
Index created.
-->插入null,'N'的記錄時失敗,提示違反唯一性約束
scott@ORCL> insert into t1 select null,'N' from dual;
insert into t1 select null,'N' from dual
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.I_T1_ID_VAL) violated
-->插入null,'Y'的記錄時同樣失敗,提示違反唯一性約束
scott@ORCL> insert into t1 select null,'Y' from dual;
insert into t1 select null,'Y' from dual
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.I_T1_ID_VAL) violated
-->插入兩個null值成功
scott@ORCL> insert into t1 select null,null from dual;
1 row created.
scott@ORCL> insert into t1 select null,null from dual;
1 row created.
scott@ORCL> insert into t1 select null,'A' from dual;
1 row created.
scott@ORCL> commit;
Commit complete.
scott@ORCL> set null unknown;
scott@ORCL> select * from t1;
ID VAL
---------- ------------------------------
unknown Y
unknown N
unknown unknown
unknown unknown
unknown A
scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T1',cascade=>true);
scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys
2 from user_indexes where table_name='T1';
INDEX_NAME INDEX_TYPE BLEVEL LEAF_BLOCKS NUM_ROWS STATUS DISTINCT_KEYS
--------------- ---------- ---------- ----------- ---------- -------- -------------
I_T1_ID NORMAL 0 0 0 VALID 0
I_T1_ID_VAL NORMAL 0 1 3 VALID 3
-->從上面的情形可知,
-->基於單列的唯一索引,可以多次插入null值,但其索引上並不存儲null值。
-->基於多列的複合索引,儘管全爲null值的行可以多次插入,但不全爲null的重複行則不能被插入(注,非唯一複合索引不存在此限制,此處不演示)。
-->基於多列的複合索引,對於全爲null值的索引值也不會被存儲。如上面的情形,儘管插入了5條記錄,複合索引中只存儲了3條。
-->注:對於唯一性約束,null值不等於null值,同樣(null,null)也不等同於(null,null),所以上面的兩次null能夠被插入。
二、null值與執行計劃- scott@ORCL> set autot trace exp;
- scott@ORCL> select * from t1 where id is null;
- Execution Plan
- ———————————————————-
- Plan hash value: 3617692013
- ————————————————————————–
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
- ————————————————————————–
- | 0 | SELECT STATEMENT | | 5 | 5 | 3 (0)| 00:00:01 |
- |* 1 | TABLE ACCESS FULL| T1 | 5 | 5 | 3 (0)| 00:00:01 |
- ————————————————————————–
- Predicate Information (identified by operation id):
- —————————————————
- 1 - filter(”ID” IS NULL)
- –>從上面的測試可知,由於null值是不被存儲的,因此當使用id is null作爲謂詞時,走了全表掃描
- scott@ORCL> select * from t1 where id is not null;
- Execution Plan
- ———————————————————-
- Plan hash value: 796913935
- —————————————————————————————
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
- —————————————————————————————
- | 0 | SELECT STATEMENT | | 1 | 1 | 0 (0)| 00:00:01 |
- | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1 | 0 (0)| 00:00:01 |
- |* 2 | INDEX FULL SCAN | I_T1_ID | 1 | | 0 (0)| 00:00:01 |
- —————————————————————————————
- Predicate Information (identified by operation id):
- —————————————————
- 2 - filter(”ID” IS NOT NULL)
- –>從上面的測試可知,儘管當前表上id列上的所有值都爲null,但不排除後續記錄插入的id不爲null的列。
- –>故當使用id is not null作爲謂詞時,此時執行計劃中走了索引全掃描。
- –>下面來看看複合索引的情形
- scott@ORCL> select * from t1 where val is null;
- Execution Plan
- ———————————————————-
- Plan hash value: 3617692013
- ————————————————————————–
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
- ————————————————————————–
- | 0 | SELECT STATEMENT | | 2 | 2 | 3 (0)| 00:00:01 |
- |* 1 | TABLE ACCESS FULL| T1 | 2 | 2 | 3 (0)| 00:00:01 |
- ————————————————————————–
- Predicate Information (identified by operation id):
- —————————————————
- 1 - filter(”VAL” IS NULL)
- scott@ORCL> select * from t1 where val is not null;
- Execution Plan
- ———————————————————-
- Plan hash value: 1931510411
- ——————————————————————————–
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
- ——————————————————————————–
- | 0 | SELECT STATEMENT | | 3 | 3 | 1 (0)| 00:00:01 |
- |* 1 | INDEX FULL SCAN | I_T1_ID_VAL | 3 | 3 | 1 (0)| 00:00:01 |
- ——————————————————————————–
- Predicate Information (identified by operation id):
- —————————————————
- 1 - filter(”VAL” IS NOT NULL)
- –>對於複合唯一索引的情形,當使用單列且非前導列謂詞時,使用is null與 is not null等同於單列唯一索引的情形。
- –>即原理也是一樣的,val is null走全表掃描而val is not null走索引。因爲null值不會被存儲。
- –>下面看看兩個列都作爲謂詞的情形
- scott@ORCL> select * from t1 where id is null and val is not null;
- Execution Plan
- ———————————————————-
- Plan hash value: 1040510552
- ——————————————————————————–
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
- ——————————————————————————–
- | 0 | SELECT STATEMENT | | 3 | 3 | 1 (0)| 00:00:01 |
- |* 1 | INDEX RANGE SCAN| I_T1_ID_VAL | 3 | 3 | 1 (0)| 00:00:01 |
- ——————————————————————————–
- Predicate Information (identified by operation id):
- —————————————————
- 1 - access(”ID” IS NULL)
- filter(”VAL” IS NOT NULL)
- –>從上面的測試可知,儘管兩個謂詞列上都存在索引,一個爲單列唯一索引,一個爲複合唯一索引。Oracle 選擇了複合索引I_T1_ID_VAL。
- scott@ORCL> select * from t1 where id is not null and val is null;
- Execution Plan
- ———————————————————-
- Plan hash value: 796913935
- —————————————————————————————
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
- —————————————————————————————
- | 0 | SELECT STATEMENT | | 1 | 1 | 0 (0)| 00:00:01 |
- |* 1 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1 | 0 (0)| 00:00:01 |
- |* 2 | INDEX FULL SCAN | I_T1_ID | 1 | | 0 (0)| 00:00:01 |
- —————————————————————————————
- Predicate Information (identified by operation id):
- —————————————————
- 1 - filter(”VAL” IS NULL)
- 2 - filter(”ID” IS NOT NULL)
- –>同樣的情形,謂詞的順序與複合索引定義的順序一樣,只不過第一個謂詞爲id is not null,而第二個謂詞爲val is null。
- –>此時Oracle 選擇了單列唯一索引I_T1_ID
- –>看到此,不知道大家是否已明白,即哪個列爲is not null,則會使用該列上的索引,原因還是那句話,索引不存儲null值。
- –>對於顛倒id列與val列以及id,val列爲null或not null的其他不同組合情形不再演示,其執行計劃類似。
scott@ORCL> set autot trace exp;
scott@ORCL> select * from t1 where id is null;
- scott@ORCL> set autot off;
- –刪除原有表上的null值記錄
- scott@ORCL> delete from t1 where val not in(‘Y’,‘N’) or val is null;
- 3 rows deleted.
- scott@ORCL> update t1 set id=1 where val=‘Y’;
- 1 row updated.
- scott@ORCL> update t1 set id=2 where val=‘N’;
- 1 row updated.
- scott@ORCL> commit;
- Commit complete.
- –>對原有記錄更新後的情形
- scott@ORCL> select * from t1;
- ID VAL
- ———- ——————————
- 1 Y
- 2 N
- scott@ORCL> exec dbms_stats.gather_table_stats(‘SCOTT’,‘T1’,cascade=>true);
- PL/SQL procedure successfully completed.
- –>修改表列id使之具有not null約束的特性
- scott@ORCL> alter table t1 modify(id not null);
- Table altered.
- scott@ORCL> set autot trace exp;
- scott@ORCL> select * from t1 where id is null;
- Execution Plan
- ———————————————————-
- Plan hash value: 3160894736
- ——————————————————————————–
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
- ——————————————————————————–
- | 0 | SELECT STATEMENT | | 1 | 5 | 0 (0)| |
- |* 1 | FILTER | | | | | |
- | 2 | INDEX FULL SCAN| I_T1_ID_VAL | 2 | 10 | 1 (0)| 00:00:01 |
- ——————————————————————————–
- Predicate Information (identified by operation id):
- —————————————————
- 1 - filter(NULL IS NOT NULL)
- –>從上面的執行計劃中可知,當表t1列id上具有not null 約束時,此時使用id is null選擇了索引範圍掃描
- –>下面來看看列val is null 的情形
- scott@ORCL> select * from t1 where val is null;
- Execution Plan
- ———————————————————-
- Plan hash value: 48744011
- ————————————————————————————
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
- ————————————————————————————
- | 0 | SELECT STATEMENT | | 1 | 5 | 2 (0)| 00:00:01 |
- |* 1 | INDEX FAST FULL SCAN| I_T1_ID_VAL | 1 | 5 | 2 (0)| 00:00:01 |
- ————————————————————————————
- Predicate Information (identified by operation id):
- —————————————————
- 1 - filter(”VAL” IS NULL)
- –>儘管val列上允許null值存在,但由於列id上具有not null 約束,且id列與val列存在複合唯一索引,因此此時選擇了索引快速全掃描
- –>其餘不同組合情形大致相同,不再演示
- –>爲表t1新增一條val爲null的記錄
- scott@ORCL> insert into t1 select 3,null from dual;
- 1 row created.
- scott@ORCL> commit;
- Commit complete.
- scott@ORCL> exec dbms_stats.gather_table_stats(‘SCOTT’,‘T1’,cascade=>true);
- PL/SQL procedure successfully completed.
- –>下面的查詢中可以看出儘管只有列id有not null約束,當所有的索引值都被存儲
- scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys
- 2 from user_indexes where table_name=‘T1’;
- INDEX_NAME INDEX_TYPE BLEVEL LEAF_BLOCKS NUM_ROWS STATUS DISTINCT_KEYS
- ————— ———- ———- ———– ———- ——– ————-
- I_T1_ID NORMAL 0 1 3 VALID 3
- I_T1_ID_VAL NORMAL 0 1 3 VALID 3
- –>Author : Robinson Cheng
- –>Blog : http://blog.csdn.net/robinson_0612
scott@ORCL> set autot off;
--刪除原有表上的null值記錄
scott@ORCL> delete from t1 where val not in('Y','N') or val is null;
3 rows deleted.
scott@ORCL> update t1 set id=1 where val='Y';
1 row updated.
scott@ORCL> update t1 set id=2 where val='N';
1 row updated.
scott@ORCL> commit;
Commit complete.
-->對原有記錄更新後的情形
scott@ORCL> select * from t1;
ID VAL
---------- ------------------------------
1 Y
2 N
scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T1',cascade=>true);
PL/SQL procedure successfully completed.
-->修改表列id使之具有not null約束的特性
scott@ORCL> alter table t1 modify(id not null);
Table altered.
scott@ORCL> set autot trace exp;
scott@ORCL> select * from t1 where id is null;
Execution Plan
----------------------------------------------------------
Plan hash value: 3160894736
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 0 (0)| |
|* 1 | FILTER | | | | | |
| 2 | INDEX FULL SCAN| I_T1_ID_VAL | 2 | 10 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(NULL IS NOT NULL)
-->從上面的執行計劃中可知,當表t1列id上具有not null 約束時,此時使用id is null選擇了索引範圍掃描
-->下面來看看列val is null 的情形
scott@ORCL> select * from t1 where val is null;
Execution Plan
----------------------------------------------------------
Plan hash value: 48744011
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 2 (0)| 00:00:01 |
|* 1 | INDEX FAST FULL SCAN| I_T1_ID_VAL | 1 | 5 | 2 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("VAL" IS NULL)
-->儘管val列上允許null值存在,但由於列id上具有not null 約束,且id列與val列存在複合唯一索引,因此此時選擇了索引快速全掃描
-->其餘不同組合情形大致相同,不再演示
-->爲表t1新增一條val爲null的記錄
scott@ORCL> insert into t1 select 3,null from dual;
1 row created.
scott@ORCL> commit;
Commit complete.
scott@ORCL> exec dbms_stats.gather_table_stats('SCOTT','T1',cascade=>true);
PL/SQL procedure successfully completed.
-->下面的查詢中可以看出儘管只有列id有not null約束,當所有的索引值都被存儲
scott@ORCL> select index_name,index_type,blevel,leaf_blocks,num_rows,status,distinct_keys
2 from user_indexes where table_name='T1';
INDEX_NAME INDEX_TYPE BLEVEL LEAF_BLOCKS NUM_ROWS STATUS DISTINCT_KEYS
--------------- ---------- ---------- ----------- ---------- -------- -------------
I_T1_ID NORMAL 0 1 3 VALID 3
I_T1_ID_VAL NORMAL 0 1 3 VALID 3
-->Author : Robinson Cheng
-->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)下的情形,不同的優化器版本可能會有偏差。
dbms_xplan之display_cursor函數的使用