非空約束重要性全表掃描的性能提升

 

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;  

  1.   
    Table created.  
  2.   
    HELLODBA.COM>alter table T_TEST1 add constraint T_TEST1_PK primary key (OBJECT_ID) using index  tablespace DEMOTSINX;  
  3.   
    Table altered.  
  4.   
    HELLODBA.COM>update t_test1 set SUBOBJECT_NAME=OBJECT_NAME where SUBOBJECT_NAME is null;  
  5.   
    32072 rows updated.  
  6.   
    HELLODBA.COM>commit;  
  7.   
    Commit complete.  
  8.   
    HELLODBA.COM>desc t_test1  
  9. Name          Null?    Type  
    ---------------------------------------------------------------------------------------------------------------------------------  
  10. OWNER         NOT NULL VARCHAR2(30)  
    OBJECT_NAME   NOT NULL VARCHAR2(30)  
  11. SUBOBJECT_NAME         VARCHAR2(30)  
    OBJECT_ID     NOT NULL NUMBER  
  12. DATA_OBJECT_ID         NUMBER  
    OBJECT_TYPE            VARCHAR2(19)  
  13. CREATED       NOT NULL DATE  
    LAST_DDL_TIME NOT NULL DATE  
  14. TIMESTAMP              VARCHAR2(19)  
    STATUS                 VARCHAR2(7)  
  15. TEMPORARY              VARCHAR2(1)  
    GENERATED              VARCHAR2(1)  
  16. SECONDARY              VARCHAR2(1)  
  17. LIO                    NUMBER  


謂詞評估    在上面表中,字段SUBOBJECT_NAME中不存在空值,但也沒有非空約束,再看以下查詢,查找該字段的空值記錄:
SQL代碼
 


HELLODBA.COM>select * from t_test1 where SUBOBJECT_NAME is null;  

  1.   
    no rows selected  
  2.   
    Execution Plan  
  3. ----------------------------------------------------------  
    Plan hash value: 1883417357  
  4.   
    -----------------------------------------------------------------------------  
  5. | Id  | Operation         | Name    | Rows  | Bytes | Cost (%CPU)| Time     |  
    -----------------------------------------------------------------------------  
  6. |   0 | SELECT STATEMENT  |         |     1 |    96 |    45   (0)| 00:00:46 |  
    |*  1 |  TABLE ACCESS FULL| T_TEST1 |     1 |    96 |    45   (0)| 00:00:46 |  
  7. -----------------------------------------------------------------------------  
      
  8. Predicate Information (identified by operation id):  
    ---------------------------------------------------  
  9.   
       1 - filter("SUBOBJECT_NAME" IS NULL)  
  10.   
    Statistics  
  11. ----------------------------------------------------------  
              0  recursive calls  
  12.           0  db block gets  
            665  consistent gets  
  13.           0  physical reads  
              0  redo size  
  14.        1048  bytes sent via SQL*Net to client  
            374  bytes received via SQL*Net from client  
  15.           1  SQL*Net roundtrips to/from client  
              0  sorts (memory)  
  16.           0  sorts (disk)  
  17.           0  rows processed  


    我們看到,需要對錶進行全表掃描(關於索引,隨後再討論)。而如果我們加上非空約束,可以看到執行計劃已經性能的變化:
SQL代碼
 


HELLODBA.COM>alter table t_test1 modify SUBOBJECT_NAME not null;  

  1.   
    Table altered.  
  2.   
    HELLODBA.COM>select * from t_test1 where SUBOBJECT_NAME is null;  
  3.   
    no rows selected  
  4.   
    Execution Plan  
  5. ----------------------------------------------------------  
    Plan hash value: 4146611218  
  6.   
    ------------------------------------------------------------------------------  
  7. | Id  | Operation          | Name    | Rows  | Bytes | Cost (%CPU)| Time     |  
    ------------------------------------------------------------------------------  
  8. |   0 | SELECT STATEMENT   |         |     1 |    96 |     0   (0)|          |  
    |*  1 |  FILTER            |         |       |       |            |          |  
  9. |   2 |   TABLE ACCESS FULL| T_TEST1 | 47585 |  4461K|    45   (0)| 00:00:46 |  
    ------------------------------------------------------------------------------  
  10.   
    Predicate Information (identified by operation id):  
  11. ---------------------------------------------------  
      
  12.    1 - filter(NULL IS NOT NULL)  
      
  13.   
    Statistics  
  14. ----------------------------------------------------------  
              0  recursive calls  
  15.           0  db block gets  
              0  consistent gets  
  16.           0  physical reads  
              0  redo size  
  17.        1048  bytes sent via SQL*Net to client  
            374  bytes received via SQL*Net from client  
  18.           1  SQL*Net roundtrips to/from client  
              0  sorts (memory)  
  19.           0  sorts (disk)  
  20.           0  rows processed  


    注意到,在全表掃描之前,增加了一個filter,而fileter的表達式是NULL IS NOT NULL,其邏輯結果是FALSE,因此,實際上其子操作(全表掃描)並未執行。相應的,請性能數據裏面CR0
    之所以優化器會爲執行計劃增加這樣一個filter,是因爲優化器在做查詢轉換(Query Transformation)時,會將非空約束作爲參照條件之一,對where子句的謂詞做邏輯結果評估,如果評估結果爲false,則會增加一個這樣的filter,以避免執行一些高代價的操作。從10053跟蹤文件中,可以看到這對於優化器對執行計劃代價估算的影響:
SQL代碼
 


Cdn, Cost adjusted (to ~ 0) as where clause evalutes to FALSE  

  1. Final - All Rows Plan:  Best join order: 1  
      Cost: 0.0000  Degree: 1  Card: 1.0000  Bytes: 4568160  
  2.   Resc: 0.0000  Resc_io: 0.0000  Resc_cpu: 0  
  3.   Resp: 0.0000  Resp_io: 0.0000  Resc_cpu: 0  


非空約束對索引選擇的影響    我們知道,OracleB*樹索引中不存在空鍵值,即在表的數據記錄中,如果索引中所有字段都爲空,則該記錄不會被構建到索引樹中。也就是說,如果索引字段上沒有非空約束,則表記錄與索引記錄不是完全映射的。
    我們先去掉subobject_name上的非空約束,並在上面建立索引:
SQL代碼
 


HELLODBA.COM>alter table t_test1 modify subobject_name null;  

  1.   
    Table altered.  
  2.   
    HELLODBA.COM>create index t_test1_subo_idx on t_test1(subobject_name) compute statistics;  
  3.   
  4. Index created.  


    執行以下語句,以獲取subobject_name最小的10條記錄。爲了提高效率,我們希望直接從索引中直接讀取前10ROWID(索引數據已經按照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;  

  1.   
    OWNER                          OBJECT_NAME                    SUBOBJECT_NAME  
  2. ------------------------------ ------------------------------ ------------------------------  
    SYS                            ICOL$                          BBB  
  3. SYS                            I_USER1                        BBB  
    SYS                            CON$                           BBB  
  4. SYS                            UNDO$                          BBB  
    SYS                            I_PROXY_ROLE_DATA$_1           BBB  
  5. SYS                            I_OBJ#                         BBB  
    SYS                            PROXY_ROLE_DATA$               BBB  
  6. SYS                            I_IND1                         BBB  
    SYS                            I_CDEF2                        BBB  
  7. SYS                            C_COBJ#                        BBB  
      
  8. 10 rows selected.  
      
  9.   
    Execution Plan  
  10. ----------------------------------------------------------  
    Plan hash value: 4050478946  
  11.   
    -----------------------------------------------------------------------------------------------  
  12. | Id  | Operation                   | Name    | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |  
    -----------------------------------------------------------------------------------------------  
  13. |   0 | SELECT STATEMENT            |         |    10 |   560 |       |   308   (1)| 00:05:09 |  
    |   1 |  NESTED LOOPS               |         |    10 |   560 |       |   308   (1)| 00:05:09 |  
  14. |   2 |   VIEW                      |         |    10 |   120 |       |   298   (1)| 00:04:59 |  
    |   3 |    SORT ORDER BY            |         |    10 |   160 |  2248K|   298   (1)| 00:04:59 |  
  15. |*  4 |     COUNT STOPKEY           |         |       |       |       |            |          |  
    |   5 |      TABLE ACCESS FULL      | T_TEST1 | 47585 |   743K|       |    45   (0)| 00:00:46 |  
  16. |   6 |   TABLE ACCESS BY USER ROWID| T_TEST1 |     1 |    44 |       |     1   (0)| 00:00:02 |  
    -----------------------------------------------------------------------------------------------  
  17.   
    Predicate Information (identified by operation id):  
  18. ---------------------------------------------------  
      
  19.    4 - filter(ROWNUM<=10)  
      
  20.   
    Statistics  
  21. ----------------------------------------------------------  
              0  recursive calls  
  22.           0  db block gets  
             14  consistent gets  
  23.           0  physical reads  
              0  redo size  
  24.         707  bytes sent via SQL*Net to client  
            385  bytes received via SQL*Net from client  
  25.           2  SQL*Net roundtrips to/from client  
              1  sorts (memory)  
  26.           0  sorts (disk)  
  27.          10  rows processed  


    但是,查詢計劃和結果看,語句並沒有按照設想的方式執行,得出的數據也不是我們需要的。其原因就在於,由於空值不被索引,優化器無法確認索引數據是否涵蓋了所有數據記錄,因而它沒有選擇指定索引。
    我們把非空約束加上,執行計劃和結果就符合我們的需求了。
SQL代碼
 


HELLODBA.COM>alter table t_test1 modify subobject_name not null;  

  1.   
    Table altered.  
  2.   
    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  
  3. order by subobject_name) v where t1.rowid=v.rid;  
      
  4. OWNER                          OBJECT_NAME                    SUBOBJECT_NAME  
    ------------------------------ ------------------------------ ------------------------------  
  5. DEMO                           NO                             A  
    DEMO                           NO                             A  
  6. DEMO                           NO                             A  
    SYS                            ICOL$                          BBB  
  7. SYS                            I_USER1                        BBB  
    SYS                            CON$                           BBB  
  8. SYS                            UNDO$                          BBB  
    SYS                            C_COBJ#                        BBB  
  9. SYS                            I_OBJ#                         BBB  
    SYS                            PROXY_ROLE_DATA$               BBB  
  10.   
    10 rows selected.  
  11.   
      
  12. Execution Plan  
    ----------------------------------------------------------  
  13. Plan hash value: 3198566056  
      
  14. ------------------------------------------------------------------------------------------------  
    | Id  | Operation                   | Name             | Rows  | Bytes | Cost (%CPU)| Time     |  
  15. ------------------------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT            |                  |    10 |   560 |    71   (0)| 00:01:11 |  
  16. |   1 |  NESTED LOOPS               |                  |    10 |   560 |    71   (0)| 00:01:11 |  
    |   2 |   VIEW                      |                  |    10 |   120 |    61   (0)| 00:01:01 |  
  17. |*  3 |    COUNT STOPKEY            |                  |       |       |            |          |  
    |   4 |     INDEX FULL SCAN         | T_TEST1_SUBO_IDX | 47585 |   743K|    61   (0)| 00:01:01 |  
  18. |   5 |   TABLE ACCESS BY USER ROWID| T_TEST1          |     1 |    44 |     1   (0)| 00:00:02 |  
    ------------------------------------------------------------------------------------------------  
  19.   
    Predicate Information (identified by operation id):  
  20. ---------------------------------------------------  
      
  21.    3 - filter(ROWNUM<=10)  
      
  22.   
    Statistics  
  23. ----------------------------------------------------------  
              0  recursive calls  
  24.           0  db block gets  
             13  consistent gets  
  25.           0  physical reads  
              0  redo size  
  26.         681  bytes sent via SQL*Net to client  
            385  bytes received via SQL*Net from client  
  27.           2  SQL*Net roundtrips to/from client  
              0  sorts (memory)  
  28.           0  sorts (disk)  
  29.          10  rows processed  


非空約束對連接查詢的影響    在進行數據關聯時,數據集中關聯字段是否存在空值也會影響優化器對執行計劃的選擇。我們再創建一張測試表。
SQL代碼
 


HELLODBA.COM>create table t_test2 tablespace DEMO as select * from dba_tables;  

  1.   
    Table created.  
  2.   
    HELLODBA.COM>alter table T_TEST2 add constraint T_TEST2_PK primary key (OWNER,TABLE_NAME) using index  tablespace DEMOTSINX;  
  3.   
    Table altered.  
  4.   
    HELLODBA.COM>desc t_test2  
  5. Name          Null?    Type  
    -------------- -------- -----------------  
  6. OWNER         NOT NULL VARCHAR2(30)  
    TABLE_NAME    NOT NULL VARCHAR2(30)  
  7. TABLESPACE_NAME        VARCHAR2(30)  
    CLUSTER_NAME           VARCHAR2(30)  
  8. IOT_NAME               VARCHAR2(30)  
    STATUS                 VARCHAR2(8)  
  9. PCT_FREE               NUMBER  
    PCT_USED               NUMBER  
  10. ...  


    再將subobject_name的非空約束去掉。
SQL代碼
 


HELLODBA.COM>alter table t_test1 modify subobject_name null;  

  1.   
  2. Table altered.  


    我們通過以下語句查找t_test1subobject_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);  

  1.   
    45135 rows selected.  
  2.   
      
  3. Execution Plan  
    ----------------------------------------------------------  
  4. Plan hash value: 3538907136  
      
  5. ------------------------------------------------------------------------------  
    | Id  | Operation          | Name    | Rows  | Bytes | Cost (%CPU)| Time     |  
  6. ------------------------------------------------------------------------------  
    |   0 | SELECT STATEMENT   |         |  2379 | 76128 |    51   (0)| 00:00:52 |  
  7. |*  1 |  FILTER            |         |       |       |            |          |  
    |   2 |   TABLE ACCESS FULL| T_TEST1 | 47585 |  1487K|    45   (0)| 00:00:46 |  
  8. |*  3 |   TABLE ACCESS FULL| T_TEST2 |     1 |    18 |     6   (0)| 00:00:07 |  
    ------------------------------------------------------------------------------  
  9.   
    Predicate Information (identified by operation id):  
  10. ---------------------------------------------------  
      
  11.    1 - filter( NOT EXISTS (SELECT /*+ */ 0 FROM "T_TEST2" "T2" WHERE  
                  LNNVL("TABLE_NAME"<>:B1)))  
  12.    3 - filter(LNNVL("TABLE_NAME"<>:B1))  
      
  13.   
    Statistics  
  14. ----------------------------------------------------------  
            392  recursive calls  
  15.           0  db block gets  
        2217674  consistent gets  
  16.           0  physical reads  
              0  redo size  
  17.     2329590  bytes sent via SQL*Net to client  
          33473  bytes received via SQL*Net from client  
  18.        3010  SQL*Net roundtrips to/from client  
              5  sorts (memory)  
  19.           0  sorts (disk)  
  20.       45135  rows processed  


    可以看到,執行計劃通過添加函數LNNVLNOT EXISTS,對數據進行過濾得到結果,性能相當低。
    注意:當邏輯表達是中的操作數可能爲空時,LNNVL函數可以判斷出該表達式的結果。
    我們再把非空約束加上,
   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章