Když používáte proměnné vazby, Oracle je nucen použít dynamické prořezávání oddílů místo statické prořezávání oddílu . Výsledkem toho je, že Oracle v době analýzy neví, ke kterým oddílům se bude přistupovat, protože se to mění na základě vašich vstupních proměnných.
To znamená, že při použití doslovných hodnot (místo vazebných proměnných) víme, ke kterým oddílům bude přistupovat váš lokální index. Proto count stopkey
lze použít na výstup indexu předtím, než ořízneme oddíly.
Při použití proměnných vazby, partition range iterator
musí zjistit, ke kterým oddílům přistupujete. Poté zkontroluje, že první z vašich proměnných v mezioperačních operacích má skutečně nižší hodnotu než druhá (filter
operace ve druhém plánu).
To lze snadno reprodukovat, jak ukazuje následující testovací případ:
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 |
---------------------------------------------------------------------
Stejně jako ve vašem příkladu může druhý dotaz filtrovat oddíly pouze na key
v čase analýzy, spíše než přesné oddíly jako v prvním příkladu.
Toto je jeden z těch vzácných případů, kdy doslovné hodnoty mohou poskytnout lepší výkon než proměnné vazby. Měli byste prozkoumat, zda je to pro vás možnost.
Nakonec řeknete, že chcete 20 řádků z každého oddílu. Váš dotaz jako stojany to neudělá, pouze vám vrátí prvních 20 řádků podle vaší objednávky. Pro 20 řádků/oddíl musíte udělat něco takového:
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
AKTUALIZACE
Důvod, proč se vám nezobrazuje count stopkey
má co dělat s filter
provoz v řádku 4 „špatného“ plánu. Jasněji to uvidíte, když zopakujete výše uvedený příklad, ale bez rozdělení.
Získáte tak následující plány:
----------------------------------------
| 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 můžete vidět, existuje další filter
operace, když používáte proměnné vazby objevující se před sort order by stopkey
. To se stane po přístupu k indexu. Toto je kontrola, že hodnoty proměnných umožní vracet data (první proměnná ve vašem mezi má ve skutečnosti nižší hodnotu než druhá). Při použití literálů to není nutné, protože optimalizátor již ví, že 50 je méně než 100 (v tomto případě). Neví však, zda je :a menší než :b v době analýzy.
Proč to přesně je, to nevím. Mohlo by se jednat o záměrný návrh společnosti Oracle – nemá smysl provádět kontrolu stopkey, pokud hodnoty nastavené pro proměnné vedou k nule řádků – nebo jen přehlédnutí.