1.1測試現象
CREATE TABLE T1 AS SELECT OBJECT_ID,OBJECT_NAME FROM DBA_OBJECTS;
CREATE TABLE T2 AS SELECT OBJECT_ID,OBJECT_NAME FROM DBA_OBJECTS;
CREATE INDEX IDX1 ON T1(OBJECT_ID);
EXPLAIN PLAN FOR
SELECT T1.OBJECT_ID FROM T1 LEFT JOIN T2 ON T1.OBJECT_ID=T2.OBJECT_ID;
COMMIT;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
Plan hash value: 1823443478
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 69236 | 1757K| | 415 (1)| 00:00:01 |
|* 1 | HASH JOIN OUTER | | 69236 | 1757K| 1696K| 415 (1)| 00:00:01 |
| 2 | TABLE ACCESS FULL| T1 | 69236 | 878K| | 124 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| T2 | 69414 | 881K| | 124 (0)| 00:00:01 |
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID"(+))
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
alter table t1 modify object_id not null;
alter table t1 add constraint ooooo check(object_id is not null);
insert into t2 select object_id,object_name from dba_objects where rownum<400
Plan hash value: 2754336310
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 3 (0)| 00:00:01 |
|* 1 | HASH JOIN OUTER | | 1 | 26 | 3 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | IDX1 | 1 | 13 | 1 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| T2 | 399 | 5187 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID"(+))
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
非空約束是字段的一個重要屬性。但是,很多時候,數據庫表的設計人員似乎並不十分在意這個屬性。最常見的現象就是,除了主鍵字段外,所有字段都不指定該屬性。而在Oracle中,默認是允許爲空。 而實際上,優化器在選擇執行計劃時,非空約束是一個重要的影響因素。爲了說明問題,我們建立以下測試表,然後分別說明非空約束在各種情況下對執行計劃和性能的影響。
SQL代碼
HELLODBA.COM>create table t_test1 tablespace DEMO as select * from dba_objects;
-
Table created. -
HELLODBA.COM>alter table T_TEST1 add constraint T_TEST1_PK primary key (OBJECT_ID) using index tablespace DEMOTSINX; -
Table altered. -
HELLODBA.COM>update t_test1 set SUBOBJECT_NAME=OBJECT_NAME where SUBOBJECT_NAME is null; -
32072 rows updated. -
HELLODBA.COM>commit; -
Commit complete. -
HELLODBA.COM>desc t_test1 - Name Null? Type
--------------------------------------------------------------------------------------------------------------------------------- - OWNER NOT NULL VARCHAR2(30)
OBJECT_NAME NOT NULL VARCHAR2(30) - SUBOBJECT_NAME VARCHAR2(30)
OBJECT_ID NOT NULL NUMBER - DATA_OBJECT_ID NUMBER
OBJECT_TYPE VARCHAR2(19) - CREATED NOT NULL DATE
LAST_DDL_TIME NOT NULL DATE - TIMESTAMP VARCHAR2(19)
STATUS VARCHAR2(7) - TEMPORARY VARCHAR2(1)
GENERATED VARCHAR2(1) - SECONDARY VARCHAR2(1)
- LIO NUMBER
謂詞評估 在上面表中,字段SUBOBJECT_NAME中不存在空值,但也沒有非空約束,再看以下查詢,查找該字段的空值記錄:
SQL代碼
HELLODBA.COM>select * from t_test1 where SUBOBJECT_NAME is null;
-
no rows selected -
Execution Plan - ----------------------------------------------------------
Plan hash value: 1883417357 -
----------------------------------------------------------------------------- - | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------- - | 0 | SELECT STATEMENT | | 1 | 96 | 45 (0)| 00:00:46 |
|* 1 | TABLE ACCESS FULL| T_TEST1 | 1 | 96 | 45 (0)| 00:00:46 | - -----------------------------------------------------------------------------
- Predicate Information (identified by operation id):
--------------------------------------------------- -
1 - filter("SUBOBJECT_NAME" IS NULL) -
Statistics - ----------------------------------------------------------
0 recursive calls - 0 db block gets
665 consistent gets - 0 physical reads
0 redo size - 1048 bytes sent via SQL*Net to client
374 bytes received via SQL*Net from client - 1 SQL*Net roundtrips to/from client
0 sorts (memory) - 0 sorts (disk)
- 0 rows processed
我們看到,需要對錶進行全表掃描(關於索引,隨後再討論)。而如果我們加上非空約束,可以看到執行計劃已經性能的變化:
SQL代碼
HELLODBA.COM>alter table t_test1 modify SUBOBJECT_NAME not null;
-
Table altered. -
HELLODBA.COM>select * from t_test1 where SUBOBJECT_NAME is null; -
no rows selected -
Execution Plan - ----------------------------------------------------------
Plan hash value: 4146611218 -
------------------------------------------------------------------------------ - | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------ - | 0 | SELECT STATEMENT | | 1 | 96 | 0 (0)| |
|* 1 | FILTER | | | | | | - | 2 | TABLE ACCESS FULL| T_TEST1 | 47585 | 4461K| 45 (0)| 00:00:46 |
------------------------------------------------------------------------------ -
Predicate Information (identified by operation id): - ---------------------------------------------------
- 1 - filter(NULL IS NOT NULL)
-
Statistics - ----------------------------------------------------------
0 recursive calls - 0 db block gets
0 consistent gets - 0 physical reads
0 redo size - 1048 bytes sent via SQL*Net to client
374 bytes received via SQL*Net from client - 1 SQL*Net roundtrips to/from client
0 sorts (memory) - 0 sorts (disk)
- 0 rows processed
注意到,在全表掃描之前,增加了一個filter,而fileter的表達式是NULL IS NOT NULL,其邏輯結果是FALSE,因此,實際上其子操作(全表掃描)並未執行。相應的,請性能數據裏面CR爲0。
之所以優化器會爲執行計劃增加這樣一個filter,是因爲優化器在做查詢轉換(Query Transformation)時,會將非空約束作爲參照條件之一,對where子句的謂詞做邏輯結果評估,如果評估結果爲false,則會增加一個這樣的filter,以避免執行一些高代價的操作。從10053跟蹤文件中,可以看到這對於優化器對執行計劃代價估算的影響:
SQL代碼
Cdn, Cost adjusted (to ~ 0) as where clause evalutes to FALSE
- Final - All Rows Plan: Best join order: 1
Cost: 0.0000 Degree: 1 Card: 1.0000 Bytes: 4568160 - Resc: 0.0000 Resc_io: 0.0000 Resc_cpu: 0
- Resp: 0.0000 Resp_io: 0.0000 Resc_cpu: 0
非空約束對索引選擇的影響 我們知道,Oracle中B*樹索引中不存在空鍵值,即在表的數據記錄中,如果索引中所有字段都爲空,則該記錄不會被構建到索引樹中。也就是說,如果索引字段上沒有非空約束,則表記錄與索引記錄不是完全映射的。
我們先去掉subobject_name上的非空約束,並在上面建立索引:
SQL代碼
HELLODBA.COM>alter table t_test1 modify subobject_name null;
-
Table altered. -
HELLODBA.COM>create index t_test1_subo_idx on t_test1(subobject_name) compute statistics; - Index created.
執行以下語句,以獲取subobject_name最小的10條記錄。爲了提高效率,我們希望直接從索引中直接讀取前10條ROWID(索引數據已經按照subobject_name排序),然後根據ROWID獲取數據記錄:
SQL代碼
HELLODBA.COM>select owner,object_name,subobject_name from t_test1 t1, (select /*+index(t t_test1_subo_idx)*/rowid rid from t_test1 t where rownum<=10order by subobject_name) v where t1.rowid=v.rid;
-
OWNER OBJECT_NAME SUBOBJECT_NAME - ------------------------------ ------------------------------ ------------------------------
SYS ICOL$ BBB - SYS I_USER1 BBB
SYS CON$ BBB - SYS UNDO$ BBB
SYS I_PROXY_ROLE_DATA$_1 BBB - SYS I_OBJ# BBB
SYS PROXY_ROLE_DATA$ BBB - SYS I_IND1 BBB
SYS I_CDEF2 BBB - SYS C_COBJ# BBB
- 10 rows selected.
-
Execution Plan - ----------------------------------------------------------
Plan hash value: 4050478946 -
----------------------------------------------------------------------------------------------- - | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------- - | 0 | SELECT STATEMENT | | 10 | 560 | | 308 (1)| 00:05:09 |
| 1 | NESTED LOOPS | | 10 | 560 | | 308 (1)| 00:05:09 | - | 2 | VIEW | | 10 | 120 | | 298 (1)| 00:04:59 |
| 3 | SORT ORDER BY | | 10 | 160 | 2248K| 298 (1)| 00:04:59 | - |* 4 | COUNT STOPKEY | | | | | | |
| 5 | TABLE ACCESS FULL | T_TEST1 | 47585 | 743K| | 45 (0)| 00:00:46 | - | 6 | TABLE ACCESS BY USER ROWID| T_TEST1 | 1 | 44 | | 1 (0)| 00:00:02 |
----------------------------------------------------------------------------------------------- -
Predicate Information (identified by operation id): - ---------------------------------------------------
- 4 - filter(ROWNUM<=10)
-
Statistics - ----------------------------------------------------------
0 recursive calls - 0 db block gets
14 consistent gets - 0 physical reads
0 redo size - 707 bytes sent via SQL*Net to client
385 bytes received via SQL*Net from client - 2 SQL*Net roundtrips to/from client
1 sorts (memory) - 0 sorts (disk)
- 10 rows processed
但是,查詢計劃和結果看,語句並沒有按照設想的方式執行,得出的數據也不是我們需要的。其原因就在於,由於空值不被索引,優化器無法確認索引數據是否涵蓋了所有數據記錄,因而它沒有選擇指定索引。
我們把非空約束加上,執行計劃和結果就符合我們的需求了。
SQL代碼
HELLODBA.COM>alter table t_test1 modify subobject_name not null;
-
Table altered. -
HELLODBA.COM>select owner,object_name,subobject_name from t_test1 t1, (select /*+index(t t_test1_subo_idx)*/rowid rid from t_test1 t where rownum<=10 - order by subobject_name) v where t1.rowid=v.rid;
- OWNER OBJECT_NAME SUBOBJECT_NAME
------------------------------ ------------------------------ ------------------------------ - DEMO NO A
DEMO NO A - DEMO NO A
SYS ICOL$ BBB - SYS I_USER1 BBB
SYS CON$ BBB - SYS UNDO$ BBB
SYS C_COBJ# BBB - SYS I_OBJ# BBB
SYS PROXY_ROLE_DATA$ BBB -
10 rows selected. -
- Execution Plan
---------------------------------------------------------- - Plan hash value: 3198566056
- ------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | - ------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 560 | 71 (0)| 00:01:11 | - | 1 | NESTED LOOPS | | 10 | 560 | 71 (0)| 00:01:11 |
| 2 | VIEW | | 10 | 120 | 61 (0)| 00:01:01 | - |* 3 | COUNT STOPKEY | | | | | |
| 4 | INDEX FULL SCAN | T_TEST1_SUBO_IDX | 47585 | 743K| 61 (0)| 00:01:01 | - | 5 | TABLE ACCESS BY USER ROWID| T_TEST1 | 1 | 44 | 1 (0)| 00:00:02 |
------------------------------------------------------------------------------------------------ -
Predicate Information (identified by operation id): - ---------------------------------------------------
- 3 - filter(ROWNUM<=10)
-
Statistics - ----------------------------------------------------------
0 recursive calls - 0 db block gets
13 consistent gets - 0 physical reads
0 redo size - 681 bytes sent via SQL*Net to client
385 bytes received via SQL*Net from client - 2 SQL*Net roundtrips to/from client
0 sorts (memory) - 0 sorts (disk)
- 10 rows processed
非空約束對連接查詢的影響 在進行數據關聯時,數據集中關聯字段是否存在空值也會影響優化器對執行計劃的選擇。我們再創建一張測試表。
SQL代碼
HELLODBA.COM>create table t_test2 tablespace DEMO as select * from dba_tables;
-
Table created. -
HELLODBA.COM>alter table T_TEST2 add constraint T_TEST2_PK primary key (OWNER,TABLE_NAME) using index tablespace DEMOTSINX; -
Table altered. -
HELLODBA.COM>desc t_test2 - Name Null? Type
-------------- -------- ----------------- - OWNER NOT NULL VARCHAR2(30)
TABLE_NAME NOT NULL VARCHAR2(30) - TABLESPACE_NAME VARCHAR2(30)
CLUSTER_NAME VARCHAR2(30) - IOT_NAME VARCHAR2(30)
STATUS VARCHAR2(8) - PCT_FREE NUMBER
PCT_USED NUMBER - ...
再將subobject_name的非空約束去掉。
SQL代碼
HELLODBA.COM>alter table t_test1 modify subobject_name null;
- Table altered.
我們通過以下語句查找t_test1中subobject_name不爲table_name的數據:
SQL代碼
HELLODBA.COM>select t1.owner, t1.object_name, t1.subobject_name from t_test1 t1 where subobject_name not in (select table_name from t_test2 t2);
-
45135 rows selected. -
- Execution Plan
---------------------------------------------------------- - Plan hash value: 3538907136
- ------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | - ------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2379 | 76128 | 51 (0)| 00:00:52 | - |* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL| T_TEST1 | 47585 | 1487K| 45 (0)| 00:00:46 | - |* 3 | TABLE ACCESS FULL| T_TEST2 | 1 | 18 | 6 (0)| 00:00:07 |
------------------------------------------------------------------------------ -
Predicate Information (identified by operation id): - ---------------------------------------------------
- 1 - filter( NOT EXISTS (SELECT /*+ */ 0 FROM "T_TEST2" "T2" WHERE
LNNVL("TABLE_NAME"<>:B1))) - 3 - filter(LNNVL("TABLE_NAME"<>:B1))
-
Statistics - ----------------------------------------------------------
392 recursive calls - 0 db block gets
2217674 consistent gets - 0 physical reads
0 redo size - 2329590 bytes sent via SQL*Net to client
33473 bytes received via SQL*Net from client - 3010 SQL*Net roundtrips to/from client
5 sorts (memory) - 0 sorts (disk)
- 45135 rows processed
可以看到,執行計劃通過添加函數LNNVL和NOT EXISTS,對數據進行過濾得到結果,性能相當低。
注意:當邏輯表達是中的操作數可能爲空時,LNNVL函數可以判斷出該表達式的結果。
我們再把非空約束加上,