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;
创建索引idx1
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,因此,实际上其子操作(全表扫描)并未执行。相应的,请性能数据里面CR为0。
之所以优化器会为执行计划增加这样一个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
非空约束对索引选择的影响 我们知道,Oracle中B*树索引中不存在空键值,即在表的数据记录中,如果索引中所有字段都为空,则该记录不会被构建到索引树中。也就是说,如果索引字段上没有非空约束,则表记录与索引记录不是完全映射的。
我们先去掉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条记录。为了提高效率,我们希望直接从索引中直接读取前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;
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_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);
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
可以看到,执行计划通过添加函数LNNVL和NOT EXISTS,对数据进行过滤得到结果,性能相当低。
注意:当逻辑表达是中的操作数可能为空时,LNNVL函数可以判断出该表达式的结果。
我们再把非空约束加上,