Hi Tom
It is my first time, hope You won't be disappointed by my question :)
Recently I tried to optimize quite complex query and the problem was witch simple between condition on varchar2 column. When variable was from the beginning of column values range the query perform really nice. When we move the value to the end number of LIO get very high and time needed to perform query becomes unacceptable.
TestCase:
create table t1(
n number,
otype varchar2(20),
nr_start varchar2(20),
nr_stop varchar2(20)
);
truncate table t1;
insert into t1 (select object_id, object_type, lpad(to_char(object_id),10,'0'), lpad(to_char(object_id+50),10,'0') from all_objects );
create index t1_typ_nr_ss on t1(otype,nr_start, nr_stop);
ANALYZE table t1 compute statistics;
ANALYZE index t1_typ_nr_ss compute statistics;
TEST@kwt > select LEAF_BLOCKS,DISTINCT_KEYS,NUM_ROWS, CLUSTERING_FACTOR from user_indexes where lower(index_name)='t1_typ_nr_ss';
LEAF_BLOCKS DISTINCT_KEYS NUM_ROWS CLUSTERING_FACTOR
----------- ------------- ---------- -----------------
271 46515 46515 802
Asking for beginning of the range (we have ~46000 records in 10gr2):
TEST@kwt > select otype,nr_start,nr_stop from t1 where otype='SYNONYM' and '0000000099' between nr_start and nr_stop;
no rows returned ...
----------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 84 | 2 (0)|
|* 1 | INDEX RANGE SCAN| T1_TYP_NR_SS | 3 | 84 | 2 (0)|
----------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OTYPE"='SYNONYM' AND "NR_STOP">='0000000099' AND
"NR_START"<='0000000099')
filter("NR_STOP">='0000000099')
Stats
----------------------------------------------------------
2 consistent gets
Very nice - 2 LIO.
Moving to the middle of range.
TEST@kwt > select otype,nr_start,nr_stop from t1 where otype='SYNONYM' and '0000021199' between nr_start and nr_stop;
OTYPE NR_START NR_STOP
-------------------- -------------------- --------------------
SYNONYM 0000021150 0000021200
SYNONYM 0000021152 0000021202
SYNONYM 0000021154 0000021204
SYNONYM 0000021156 0000021206
SYNONYM 0000021158 0000021208
SYNONYM 0000021160 0000021210
SYNONYM 0000021162 0000021212
SYNONYM 0000021164 0000021214
SYNONYM 0000021166 0000021216
SYNONYM 0000021168 0000021218
SYNONYM 0000021170 0000021220
SYNONYM 0000021172 0000021222
SYNONYM 0000021174 0000021224
SYNONYM 0000021176 0000021226
SYNONYM 0000021178 0000021228
SYNONYM 0000021180 0000021230
SYNONYM 0000021182 0000021232
SYNONYM 0000021184 0000021234
SYNONYM 0000021186 0000021236
SYNONYM 0000021188 0000021238
SYNONYM 0000021190 0000021240
SYNONYM 0000021192 0000021242
SYNONYM 0000021194 0000021244
SYNONYM 0000021196 0000021246
SYNONYM 0000021198 0000021248
25 wierszy zosta¿o wybranych.
Plan wykonywania
----------------------------------------------------------
Plan hash value: 2565012117
----------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 399 | 11172 | 5 (0)|
|* 1 | INDEX RANGE SCAN| T1_TYP_NR_SS | 399 | 11172 | 5 (0)|
----------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OTYPE"='SYNONYM' AND "NR_STOP">='0000021199' AND
"NR_START"<='0000021199')
filter("NR_STOP">='0000021199')
Stats
----------------------------------------------------------
49 consistent gets
LIO gets up.
Moving to the end of range.
TEST@kwt > select otype,nr_start,nr_stop from t1 where otype='SYNONYM' and '0000046199' between nr_start and nr_stop;
OTYPE NR_START NR_STOP
-------------------- -------------------- --------------------
SYNONYM 0000046152 0000046202
SYNONYM 0000046153 0000046203
SYNONYM 0000046154 0000046204
SYNONYM 0000046155 0000046205
SYNONYM 0000046156 0000046206
SYNONYM 0000046157 0000046207
SYNONYM 0000046158 0000046208
SYNONYM 0000046159 0000046209
SYNONYM 0000046160 0000046210
SYNONYM 0000046161 0000046211
SYNONYM 0000046162 0000046212
SYNONYM 0000046163 0000046213
SYNONYM 0000046164 0000046214
SYNONYM 0000046165 0000046215
SYNONYM 0000046166 0000046216
SYNONYM 0000046167 0000046217
SYNONYM 0000046168 0000046218
SYNONYM 0000046169 0000046219
SYNONYM 0000046170 0000046220
SYNONYM 0000046171 0000046221
SYNONYM 0000046176 0000046226
21 wierszy zosta¿o wybranych.
Plan wykonywania
----------------------------------------------------------
Plan hash value: 2565012117
----------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 186 | 5208 | 10 (0)|
|* 1 | INDEX RANGE SCAN| T1_TYP_NR_SS | 186 | 5208 | 10 (0)|
----------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OTYPE"='SYNONYM' AND "NR_STOP">='0000046199' AND
"NR_START"<='0000046199')
filter("NR_STOP">='0000046199')
Stats
----------------------------------------------------------
110 consistent gets
110 LIO for 21 record with simple condition?
After some googling found some workaround - but works only for limited number of rows:
TEST@kwt > select <b>/*+ INDEX_DESC(t1 t1_typ_nr_ss) */</b>
2 otype,nr_start,nr_stop from t1 where otype='SYNONYM' and '0000046199' between nr_start and nr_stop <b>and ROWNUM<10</b>
3 ;
OTYPE NR_START NR_STOP
-------------------- -------------------- --------------------
SYNONYM 0000046176 0000046226
SYNONYM 0000046171 0000046221
SYNONYM 0000046170 0000046220
SYNONYM 0000046169 0000046219
SYNONYM 0000046168 0000046218
SYNONYM 0000046167 0000046217
SYNONYM 0000046166 0000046216
SYNONYM 0000046165 0000046215
SYNONYM 0000046164 0000046214
9 wierszy zosta¿o wybranych.
Plan wykonywania
----------------------------------------------------------
Plan hash value: 2702594328
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |Cost
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 252 | 2
|* 1 | COUNT STOPKEY | | | |
|* 2 | INDEX RANGE SCAN DESCENDING| T1_TYP_NR_SS | 10 | 280 | 2
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<10)
2 - access("OTYPE"='SYNONYM' AND "NR_STOP">='0000046199' AND
"NR_START"<='0000046199')
filter("NR_STOP">='0000046199')
Statystyki
----------------------------------------------------------
<b>3</b> consistent gets
It was not acceptable for me, because I need all rows meeting this condition.
So, when asking for end of range, Oracle read all index entries for given otype.
TEST@kwt > select count(*) from t1 where otype='SYNONYM';
COUNT(*)
----------
20092
Plan wykonywania
----------------------------------------------------------
Plan hash value: 1921597393
-----------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| -----------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 8 | 11 (0)|
| 1 | SORT AGGREGATE | | 1 | 8 | |
|* 2 | INDEX RANGE SCAN| T1_TYP_NR_SS | 1661 | 13288 | 11 (0)|
-----------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OTYPE"='SYNONYM')
Statystyki
----------------------------------------------------------
<b>116</b> consistent gets
TEST@kwt > select otype,nr_start,nr_stop from t1 where otype='SYNONYM' and '<b>0000099199</b>' between nr_start and nr_stop;
nie wybrano ¿adnych wierszy
Plan wykonywania
----------------------------------------------------------
Plan hash value: 2565012117
----------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 28 | 11 (0)|
|* 1 | INDEX RANGE SCAN| T1_TYP_NR_SS | 1 | 28 | 11 (0)|
----------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OTYPE"='SYNONYM' AND "NR_STOP">='0000099199' AND
"NR_START"<='0000099199')
filter("NR_STOP">='0000099199')
Statystyki
----------------------------------------------------------
<b>116</b> consistent gets
Can You please explain, why Oracle have to do this ?
Adding additional condition "and nr_stop and nr_stop>=nr_start" change only filtering part, optimizer do not use this knowledge of correlation between columns nr_stop and nr_start.
Is this possible to make optimizer to do something like this :
1) look for otype (as currently)
2) look for the first value of nr_start > :X (index desc?)
3) go down until nr_stop < :X
4) records found between 2 and 3 steps are our result
Does it make sense, or I miss something?
What are other ways to tune query like this ?
greetings from Poland
Your admirer
Krzysztof Wlasiuk