Gdy używasz zmiennych wiązania, Oracle jest zmuszony do używania dynamiczne przycinanie partycji zamiast przycinanie partycji statycznych . Wynikiem tego jest to, że Oracle nie wie w czasie analizy, które partycje będą dostępne, ponieważ zmienia się to w zależności od zmiennych wejściowych.
Oznacza to, że używając wartości literalnych (zamiast zmiennych wiążących), wiemy, do których partycji będzie miał dostęp Twój indeks lokalny. Dlatego count stopkey
można zastosować do wyjścia indeksu przed przycięciem partycji.
Podczas korzystania ze zmiennych wiązania partition range iterator
musi dowiedzieć się, do których partycji uzyskujesz dostęp. Następnie sprawdza, czy pierwsza z twoich zmiennych pomiędzy operacjami faktycznie ma niższą wartość niż druga (filter
w drugim planie).
Można to łatwo odtworzyć, jak pokazuje następujący przypadek testowy:
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 |
---------------------------------------------------------------------
Podobnie jak w twoim przykładzie, drugie zapytanie może filtrować tylko partycje do key
w czasie analizy, zamiast dokładnych partycji, jak w pierwszym przykładzie.
Jest to jeden z tych rzadkich przypadków, w których wartości dosłowne mogą zapewnić lepszą wydajność niż powiązanie zmiennych. Powinieneś sprawdzić, czy jest to dla Ciebie możliwe.
Na koniec mówisz, że chcesz 20 wierszy z każdej partycji. Twoje zapytanie jako stand tego nie zrobi, po prostu zwróci Ci pierwsze 20 wierszy zgodnie z zamówieniem. Dla 20 wierszy/partycji musisz zrobić coś takiego:
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
AKTUALIZUJ
Powód, dla którego nie otrzymujesz count stopkey
dotyczy filter
operacja w linii 4 „złego” planu. Możesz to zobaczyć wyraźniej, jeśli powtórzysz powyższy przykład, ale bez partycjonowania.
Daje to następujące plany:
----------------------------------------
| 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'))
Jak widać, istnieje dodatkowy filter
operacja, gdy używasz bindowanych zmiennych pojawiających się przed sort order by stopkey
. Dzieje się tak po uzyskaniu dostępu do indeksu. Jest to sprawdzanie, czy wartości zmiennych pozwolą na zwrócenie danych (pierwsza zmienna w między nimi faktycznie ma niższą wartość niż druga). Nie jest to konieczne w przypadku używania literałów, ponieważ optymalizator już wie, że 50 to mniej niż 100 (w tym przypadku). Nie wie jednak, czy :a jest mniejsze niż :b w czasie analizy.
Dlaczego dokładnie tak jest, nie wiem. Może to być celowy projekt firmy Oracle — nie ma sensu sprawdzać klawisza stopu, jeśli wartości ustawione dla zmiennych dają zero wierszy — lub po prostu przeoczenie.