Когато използвате променливи за свързване, Oracle е принуден да използва динамично подрязване на дял вместо статично съкращаване на дял . Резултатът от това е, че Oracle не знае по време на анализ кои дялове ще бъдат достъпни, тъй като това се променя въз основа на вашите входни променливи.
Това означава, че когато използваме литерални стойности (вместо свързващи променливи), ние знаем кои дялове ще бъдат достъпни от вашия локален индекс. Следователно count stopkey
може да се приложи към изхода на индекса, преди да изрежем дяловете.
Когато използвате променливи за обвързване, partition range iterator
трябва да разбере кои дялове имате достъп. След това има проверка, за да гарантира, че първата от вашите променливи в операциите между операциите наистина има по-ниска стойност от втората (filter
операция във втори план).
Това може лесно да се възпроизведе, както показва следният тестов случай:
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 |
---------------------------------------------------------------------
Както във вашия пример, втората заявка може да филтрира само дяловете към key
по време на анализ, а не точните дялове, както в първия пример.
Това е един от онези редки случаи, при които литералните стойности могат да осигурят по-добра производителност от свързващите променливи. Трябва да проучите дали това е възможност за вас.
Накрая казвате, че искате 20 реда от всеки дял. Вашата заявка като stands няма да направи това, тя просто ще ви върне първите 20 реда според вашата поръчка. За 20 реда/дял трябва да направите нещо подобно:
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
АКТУАЛИЗАЦИЯ
Причината, поради която не получавате count stopkey
е свързано с filter
операция в ред 4 от "лошия" план. Можете да видите това по-ясно, ако повторите примера по-горе, но без разделяне.
Това ви дава следните планове:
----------------------------------------
| 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'))
Както можете да видите, има допълнителен filter
операция, когато използвате свързващи променливи, появяващи се преди sort order by stopkey
. Това се случва след достъп до индекса. Това е проверка дали стойностите за променливите ще позволят връщането на данни (първата променлива във вашия между всъщност има по-ниска стойност от втората). Това не е необходимо, когато използвате литерали, защото оптимизаторът вече знае, че 50 е по-малко от 100 (в този случай). Въпреки това не знае дали :a е по-малко от :b по време на анализ.
Защо точно това не знам. Това може да е умишлен дизайн от Oracle - няма смисъл да се прави проверка на стоп клавиша, ако зададените стойности за променливите водят до нула редове - или просто пропуск.