Wanneer u bindvariabelen gebruikt, wordt Oracle gedwongen om te gebruiken dynamische partitie snoeien in plaats van snoeien van statische partities . Het resultaat hiervan is dat Oracle tijdens het parseren niet weet welke partities worden benaderd, omdat dit verandert op basis van uw invoervariabelen.
Dit betekent dat wanneer we letterlijke waarden gebruiken (in plaats van bindvariabelen), we weten welke partities worden benaderd door uw lokale index. Daarom is de count stopkey
kan worden toegepast op de uitvoer van de index voordat we de partities snoeien.
Bij gebruik van bindvariabelen, de partition range iterator
moet uitzoeken tot welke partities u toegang hebt. Vervolgens wordt er gecontroleerd of de eerste van uw variabelen in de tussenbewerkingen daadwerkelijk een lagere waarde heeft dan de tweede (het filter
operatie in het tweede plan).
Dit kan eenvoudig worden gereproduceerd, zoals blijkt uit de volgende testcase:
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 |
---------------------------------------------------------------------
Net als in uw voorbeeld, kan de tweede query alleen de partities filteren op een key
tijdens het parseren, in plaats van de exacte partities zoals in het eerste voorbeeld.
Dit is een van die zeldzame gevallen waarin letterlijke waarden betere prestaties kunnen leveren dan bindvariabelen. Je moet onderzoeken of dit voor jou een mogelijkheid is.
Ten slotte zegt u dat u 20 rijen van elke partitie wilt. Uw zoekopdracht als standaard zal dit niet doen, het zal u alleen de eerste 20 rijen retourneren volgens uw bestelling. Voor 20 rijen/partitie moet je zoiets als dit doen:
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
UPDATE
De reden waarom je de count stopkey
niet krijgt heeft te maken met het filter
operatie in regel 4 van het "slechte" plan. U kunt dit duidelijker zien als u het bovenstaande voorbeeld herhaalt, maar zonder partitionering.
Dit geeft je de volgende plannen:
----------------------------------------
| 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'))
Zoals je kunt zien, is er een extra filter
bewerking wanneer u bindvariabelen gebruikt die verschijnen vóór de sort order by stopkey
. Dit gebeurt na toegang tot de index. Hiermee wordt gecontroleerd of de waarden voor de variabelen het mogelijk maken om gegevens te retourneren (de eerste variabele in uw tussenliggende heeft eigenlijk een lagere waarde dan de tweede). Dit is niet nodig bij het gebruik van letterlijke waarden, omdat de optimizer al weet dat 50 minder is dan 100 (in dit geval). Het weet echter niet of :a kleiner is dan :b tijdens de parse-tijd.
Waarom dit precies is weet ik niet. Het kan een opzettelijk ontwerp van Oracle zijn - het heeft geen zin om de stopkey-controle uit te voeren als de waarden die zijn ingesteld voor de variabelen resulteren in nul rijen - of gewoon een vergissing.