Had discussed
about the importance of NOT NULL constraints in-terms of query rewrite
capabilities in the past.
The following example appeared as part of real production
scenario, where a simple predicate against the sub-partition key columns
doesn’t perform partition elimination.
rajesh@ORA11G> create table t
2 partition by range( end_dt )
3 subpartition by range( start_dt )
4 (
5 partition p_2014 values less than
6 (
to_date('01-Jan-2015','dd-mon-yyyy') )
7 (
8 subpartition sp_2014_q1 values
less than
9 (
to_date('01-apr-2014','dd-mon-yyyy') ) ,
10 subpartition sp_2014_q2 values
less than
11 (
to_date('01-jul-2014','dd-mon-yyyy') ) ,
12 subpartition sp_2014_q3 values
less than
13 (
to_date('01-oct-2014','dd-mon-yyyy') ) ,
14 subpartition sp_2014_q4 values less than
15 (
to_date('01-jan-2015','dd-mon-yyyy') )
16 ) ,
17 partition p_2015 values less than
18 (
to_date('01-Jan-2016','dd-mon-yyyy') )
19 (
20 subpartition sp_2015_q1 values
less than
21 (
to_date('01-apr-2015','dd-mon-yyyy') ) ,
22 subpartition sp_2015_q2 values
less than
23 (
to_date('01-jul-2015','dd-mon-yyyy') ) ,
24 subpartition sp_2015_q3 values less than
25 (
to_date('01-oct-2015','dd-mon-yyyy') ) ,
26 subpartition sp_2015_q4 values
less than
27 (
to_date('01-jan-2016','dd-mon-yyyy') )
28 )
29 )
30 as
31 select a.* ,
32 to_date('01-Jan-2014','dd-mon-yyyy') +
mod(rownum,730)-1 as start_dt,
33 to_date('01-Jan-2014','dd-mon-yyyy') +
mod(rownum,730) as end_dt
34 from all_objects a;
Table created.
rajesh@ORA11G> alter table t add constraint t_chk check(
start_dt < end_dt );
Table altered.
Given the fact that, table is partitioned by END_DT and sub partitioned
by START_DT and a check constraint to enforce that START_DT is always less than
END_DT.
rajesh@ORA11G> set autotrace traceonly explain
rajesh@ORA11G> select *
2 from t
3 where start_dt =
to_date('13-Jan-2015','dd-mon-yyyy') ;
Execution Plan
----------------------------------------------------------
Plan hash value: 2183203583
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Pstart| Pstop |
-------------------------------------------------------------------------------------
| 0 | SELECT
STATEMENT |
| 136 | 23936 | 58
(6)| | |
| 1 | PARTITION RANGE
ALL | |
136 | 23936 | 58 (6)|
1 | 2 |
| 2 | PARTITION RANGE SINGLE| |
136 | 23936 | 58 (6)|
| |
|* 3 | TABLE ACCESS FULL | T
| 136 | 23936 | 58
(6)| | |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("START_DT"=TO_DATE('
2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
The above sql has a filter against the START_DT column, but why should
it hit both the partitions, can’t the optimizer do something like this using
metadata and do partition elimination?
So given the filter start_dt = 13-Jan-2015, and the
presence of check constraint can produce the additional transitive filter like
this (end_dt
> 13-Jan-2015)
o
13-Jan-2015 less than 01-Jan-2015 – condition
false, so skip scanning first partition.
o
13-Jan-2015 less than 01-Jan-2016 – condition
true, so scan this partition and its relevant sub-partition to produce results.
But the optimizer doesn’t perform that. Instead it hit both the
partitions.
Peeking into the 10053 trace file, produced some clue on this (highlighted
below).
**************************
PM: PM bypassed: Outer
query contains no views.
PM: PM bypassed: Outer
query contains no views.
query block SEL$1 (#0) unchanged
FPD: Considering simple filter push in query block
SEL$1 (#0)
"T"."START_DT"=TO_DATE('
2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
try to generate transitive predicate from check
constraints for query block SEL$1 (#0)
constraint:
"T"."START_DT"<"T"."END_DT"
finally: "T"."START_DT"=TO_DATE('
2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
apadrv-start sqlid=18016176355183581983
:
call(in-use=2136,
alloc=16344), compile(in-use=67952, alloc=72680), execution(in-use=109984,
alloc=113960)
*******************************************
Peeked values of the binds in SQL statement
*******************************************
Final query after transformations:******* UNPARSED QUERY IS
*******
SELECT "T"."OWNER" "OWNER",
"T"."OBJECT_NAME" "OBJECT_NAME",
"T"."SUBOBJECT_NAME" "SUBOBJECT_NAME",
"T"."OBJECT_ID" "OBJECT_ID",
"T"."DATA_OBJECT_ID" "DATA_OBJECT_ID",
"T"."OBJECT_TYPE" "OBJECT_TYPE",
"T"."CREATED" "CREATED",
"T"."LAST_DDL_TIME" "LAST_DDL_TIME",
"T"."TIMESTAMP" "TIMESTAMP",
"T"."STATUS" "STATUS",
"T"."TEMPORARY" "TEMPORARY",
"T"."GENERATED" "GENERATED",
"T"."SECONDARY" "SECONDARY",
"T"."NAMESPACE" "NAMESPACE",
"T"."EDITION_NAME" "EDITION_NAME",
"T"."START_DT" "START_DT",
"T"."END_DT" "END_DT"
FROM "RAJESH"."T" "T"
WHERE "T"."START_DT"=TO_DATE('
2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
Now let’s add a NOT NULL constraints on the partition key columns and
rerun our sql to see the plan it picks.
rajesh@ORA11G> alter table t modify END_DT NOT NULL;
Table altered.
rajesh@ORA11G> set autotrace traceonly explain
rajesh@ORA11G> select *
2 from t
3 where start_dt =
to_date('13-Jan-2015','dd-mon-yyyy') ;
Execution Plan
----------------------------------------------------------
Plan hash value: 3874588989
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Pstart| Pstop |
-------------------------------------------------------------------------------------
| 0 | SELECT
STATEMENT | |
136 | 23936 | 55 (0)|
| |
| 1 | PARTITION RANGE
SINGLE | | 136 | 23936 | 55
(0)| 2 | 2 |
| 2 | PARTITION RANGE SINGLE| |
136 | 23936 | 55 (0)|
1 | 1 |
|* 3 | TABLE ACCESS FULL | T
| 136 | 23936 | 55
(0)| 5 | 5 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 -
filter("START_DT"=TO_DATE(' 2015-01-13 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND
"END_DT">TO_DATE(' 2015-01-13 00:00:00', 'syyyy-mm-dd
hh24:mi:ss'))
So given the NOT NULL constraint on the END_DT column and the presence
of check constraint, helped the optimizer to transparently rewrite the given
condition
start_dt = to_date('13-Jan-2015','dd-mon-yyyy')
as
start_dt = to_date('13-Jan-2015','dd-mon-yyyy') and end_dt > to_date('13-Jan-2015','dd-mon-yyyy')
Since the end_dt > to_date('13-Jan-2015','dd-mon-yyyy'), it applied
this criteria to eliminate the first partition from scanning and hit straight
into the Second partition, and within that it only scan the relevant
sub-partition required for this SQL.
And the 10053 trace file confirms the same.
FPD: Considering simple filter push in query block SEL$1 (#0)
"T"."START_DT"=TO_DATE(' 2015-01-13
00:00:00', 'syyyy-mm-dd hh24:mi:ss')
try to generate transitive predicate from check constraints for
query block SEL$1 (#0)
constraint:
"T"."START_DT"<"T"."END_DT"
finally:
"T"."START_DT"=TO_DATE(' 2015-01-13 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND "T"."END_DT">TO_DATE(' 2015-01-13
00:00:00', 'syyyy-mm-dd hh24:mi:ss')
FPD: transitive
predicates are generated in query block SEL$1 (#0)
"T"."START_DT"=TO_DATE(' 2015-01-13
00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
"T"."END_DT">TO_DATE(' 2015-01-13 00:00:00',
'syyyy-mm-dd hh24:mi:ss')
apadrv-start sqlid=18016176355183581983
Final query AFTER transformations:******* UNPARSED QUERY
IS
*******
SELECT
"T"."OWNER" "OWNER",
"T"."OBJECT_NAME" "OBJECT_NAME",
"T"."SUBOBJECT_NAME" "SUBOBJECT_NAME",
"T"."OBJECT_ID" "OBJECT_ID",
"T"."DATA_OBJECT_ID" "DATA_OBJECT_ID",
"T"."OBJECT_TYPE" "OBJECT_TYPE",
"T"."CREATED" "CREATED",
"T"."LAST_DDL_TIME" "LAST_DDL_TIME",
"T"."TIMESTAMP" "TIMESTAMP",
"T"."STATUS" "STATUS",
"T"."TEMPORARY" "TEMPORARY",
"T"."GENERATED" "GENERATED",
"T"."SECONDARY" "SECONDARY",
"T"."NAMESPACE" "NAMESPACE",
"T"."EDITION_NAME" "EDITION_NAME",
"T"."START_DT" "START_DT",
"T"."END_DT" "END_DT"
FROM
"RAJESH"."T" "T"
WHERE "T"."START_DT"=TO_DATE(' 2015-01-13
00:00:00', 'syyyy-mm-dd hh24:mi:ss')
AND
"T"."END_DT"
>TO_DATE(' 2015-01-13 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
All these sort of partition elimination (pruning) is possible only with
NOT NULL constraint in place.
The dreaded NOT NULL constraint strikes again!
No comments:
Post a Comment