När du använder bindningsvariabler tvingas Oracle att använda dynamisk partitionsbeskärning istället för statisk partitionsbeskärning . Resultatet av detta är att Oracle vid analys inte vet vilka partitioner som kommer att nås, eftersom detta ändras baserat på dina indatavariabler.
Det betyder att när vi använder bokstavliga värden (istället för bindningsvariabler) vet vi vilka partitioner som kommer att nås av ditt lokala index. Därför count stopkey
kan appliceras på utdata från indexet innan vi beskär partitionerna.
När du använder bindningsvariabler, partition range iterator
måste ta reda på vilka partitioner du kommer åt. Den har sedan en kontroll för att säkerställa att den första av dina variabler i mellanoperationerna faktiskt har ett lägre värde än den andra (filter
operation i den andra planen).
Detta kan enkelt reproduceras, vilket följande testfall visar:
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 ditt exempel kan den andra frågan bara filtrera partitionerna till en key
vid analystid, snarare än de exakta partitionerna som i det första exemplet.
Detta är ett av de sällsynta fall där bokstavliga värden kan ge bättre prestanda än bindningsvariabler. Du bör undersöka om detta är en möjlighet för dig.
Slutligen säger du att du vill ha 20 rader från varje partition. Din fråga som står kommer inte att göra detta, den returnerar bara de första 20 raderna enligt din beställning. För 20 rader/partition måste du göra något så här:
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
UPPDATERA
Anledningen till att du inte får count stopkey
har att göra med filter
operation i rad 4 i den "dåliga" planen. Du kan se detta tydligare om du upprepar exemplet ovan, men utan partitionering.
Detta ger dig följande 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 finns det ett extra filter
operation när du använder bindningsvariabler som visas före sort order by stopkey
. Detta händer efter att du har öppnat indexet. Detta är att kontrollera att värdena för variablerna tillåter att data returneras (den första variabeln i din mellan har faktiskt ett lägre värde än den andra). Detta är inte nödvändigt när du använder bokstaver eftersom optimeraren redan vet att 50 är mindre än 100 (i det här fallet). Den vet dock inte om :a är mindre än :b vid analystid.
Varför exakt detta vet jag inte. Det kan vara avsiktlig design av Oracle - det är ingen idé att göra stoppnyckelkontrollen om värdena som ställts in för variablerna resulterar i noll rader - eller bara en förbiseende.