Når du bruger bindevariabler, er Oracle tvunget til at bruge dynamisk partitionsbeskæring i stedet for statisk partitionsbeskæring . Resultatet af dette er, at Oracle ikke ved parsetidspunktet, hvilke partitioner der vil blive tilgået, da dette ændres baseret på dine inputvariabler.
Det betyder, at når vi bruger bogstavelige værdier (i stedet for bindevariabler), ved vi, hvilke partitioner der vil blive tilgået af dit lokale indeks. Derfor count stopkey
kan anvendes på outputtet af indekset, før vi beskærer partitionerne.
Når du bruger bindevariabler, partition range iterator
skal finde ud af, hvilke partitioner du har adgang til. Den har derefter en kontrol for at sikre, at den første af dine variabler i mellem-handlingerne faktisk har en lavere værdi end den anden (filter
operation i den anden plan).
Dette kan nemt reproduceres, som følgende testcase viser:
create table tab (
x date,
y integer,
filler varchar2(100)
) partition by range(x) (
partition p1 values less than (date'2013-01-01'),
partition p2 values less than (date'2013-02-01'),
partition p3 values less than (date'2013-03-01'),
partition p4 values less than (date'2013-04-01'),
partition p5 values less than (date'2013-05-01'),
partition p6 values less than (date'2013-06-01')
);
insert into tab (x, y)
select add_months(trunc(sysdate, 'y'), mod(rownum, 5)), rownum, dbms_random.string('x', 50)
from dual
connect by level <= 1000;
create index i on tab(x desc, y desc) local;
exec dbms_stats.gather_table_stats(user, 'tab', cascade => true);
explain plan for
SELECT * FROM (
SELECT rowid FROM tab
where x between date'2013-01-01' and date'2013-02-02'
and y between 50 and 100
order by x desc, y desc
)
where rownum <= 5;
SELECT * FROM table(dbms_xplan.display(null, null, 'BASIC +ROWS +PARTITION'));
--------------------------------------------------------------------
| Id | Operation | Name | Rows | Pstart| Pstop |
--------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | |
| 1 | COUNT STOPKEY | | | | |
| 2 | VIEW | | 1 | | |
| 3 | SORT ORDER BY STOPKEY | | 1 | | |
| 4 | PARTITION RANGE ITERATOR| | 1 | 2 | 3 |
| 5 | COUNT STOPKEY | | | | |
| 6 | INDEX RANGE SCAN | I | 1 | 2 | 3 |
--------------------------------------------------------------------
explain plan for
SELECT * FROM (
SELECT rowid FROM tab
where x between to_date(:st, 'dd/mm/yyyy') and to_date(:en, 'dd/mm/yyyy')
and y between :a and :b
order by x desc, y desc
)
where rownum <= 5;
SELECT * FROM table(dbms_xplan.display(null, null, 'BASIC +ROWS +PARTITION'));
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Pstart| Pstop |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | |
| 1 | COUNT STOPKEY | | | | |
| 2 | VIEW | | 1 | | |
| 3 | SORT ORDER BY STOPKEY | | 1 | | |
| 4 | FILTER | | | | |
| 5 | PARTITION RANGE ITERATOR| | 1 | KEY | KEY |
| 6 | INDEX RANGE SCAN | I | 1 | KEY | KEY |
---------------------------------------------------------------------
Som i dit eksempel kan den anden forespørgsel kun filtrere partitionerne til en key
på parsetid, snarere end de nøjagtige partitioner som i det første eksempel.
Dette er et af de sjældne tilfælde, hvor bogstavelige værdier kan give bedre ydeevne end bindevariabler. Du bør undersøge, om dette er en mulighed for dig.
Til sidst siger du, at du vil have 20 rækker fra hver partition. Din forespørgsel som stands vil ikke gøre dette, den vil bare returnere dig de første 20 rækker i henhold til din bestilling. For 20 rækker/partition skal du gøre noget som dette:
select rd from (
select rowid rd,
row_number() over (partition by trx_id order by create_ts desc) rn
from OUT_SMS
where TRX_ID between ? and ?
and CREATE_TS between ? and ?
order by CREATE_TS DESC, TRX_ID DESC
) where rn <= 20
OPDATERING
Årsagen til, at du ikke får count stopkey
er at gøre med filter
drift i linje 4 i den "dårlige" plan. Du kan se dette tydeligere, hvis du gentager eksemplet ovenfor, men uden partitionering.
Dette giver dig følgende planer:
----------------------------------------
| Id | Operation | Name |
----------------------------------------
| 0 | SELECT STATEMENT | |
|* 1 | COUNT STOPKEY | |
| 2 | VIEW | |
|* 3 | SORT ORDER BY STOPKEY| |
|* 4 | TABLE ACCESS FULL | TAB |
----------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=5)
3 - filter(ROWNUM<=5)
4 - filter("X">=TO_DATE(' 2013-01-01 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND "X"<=TO_DATE(' 2013-02-02 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND "Y">=50 AND "Y"<=100)
----------------------------------------
| Id | Operation | Name |
----------------------------------------
| 0 | SELECT STATEMENT | |
|* 1 | COUNT STOPKEY | |
| 2 | VIEW | |
|* 3 | SORT ORDER BY STOPKEY| |
|* 4 | FILTER | |
|* 5 | TABLE ACCESS FULL | TAB |
----------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=5)
3 - filter(ROWNUM<=5)
4 - filter(TO_NUMBER(:A)<=TO_NUMBER(:B) AND
TO_DATE(:ST,'dd/mm/yyyy')<=TO_DATE(:EN,'dd/mm/yyyy'))
5 - filter("Y">=TO_NUMBER(:A) AND "Y"<=TO_NUMBER(:B) AND
"X">=TO_DATE(:ST,'dd/mm/yyyy') AND "X"<=TO_DATE(:EN,'dd/mm/yyyy'))
Som du kan se, er der et ekstra filter
operation, når du bruger bindevariabler, der vises før sort order by stopkey
. Dette sker efter adgang til indekset. Dette er at kontrollere, at værdierne for variablerne vil tillade, at data returneres (den første variabel i din mellem har faktisk en lavere værdi end den anden). Dette er ikke nødvendigt, når du bruger bogstaver, fordi optimeringsværktøjet allerede ved, at 50 er mindre end 100 (i dette tilfælde). Den ved dog ikke, om :a er mindre end :b på parsetidspunktet.
Hvorfor det præcist er det ved jeg ikke. Det kan være bevidst design af Oracle - det nytter ikke noget at foretage stoptasten, hvis værdierne for variablerne resulterer i nul rækker - eller bare en forglemmelse.