Quando você usa variáveis de ligação, a Oracle é forçada a usar remoção de partição dinâmica em vez de remoção de partição estática . O resultado disso é que o Oracle não sabe no momento da análise quais partições serão acessadas, pois isso muda com base em suas variáveis de entrada.
Isso significa que ao usar valores literais (em vez de variáveis de ligação), sabemos quais partições serão acessadas pelo seu índice local. Portanto, a
count stopkey
pode ser aplicado à saída do índice antes de podarmos as partições. Ao usar variáveis de ligação, o
partition range iterator
tem que descobrir quais partições você está acessando. Ele então tem uma verificação para garantir que a primeira de suas variáveis entre as operações realmente tenha um valor menor que a segunda (o filter
operação no segundo plano). Isso pode ser facilmente reproduzido, como mostra o caso de teste a seguir:
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 |
---------------------------------------------------------------------
Como no seu exemplo, a segunda consulta só pode filtrar as partições para uma
key
no momento da análise, em vez das partições exatas como no primeiro exemplo. Este é um daqueles casos raros em que valores literais podem fornecer melhor desempenho do que variáveis de associação. Você deve investigar se esta é uma possibilidade para você.
Por fim, você diz que deseja 20 linhas de cada partição. Sua consulta como estandes não fará isso, apenas retornará as primeiras 20 linhas de acordo com seu pedido. Para 20 linhas/partição, você precisa fazer algo assim:
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
ATUALIZAÇÃO
O motivo pelo qual você não está recebendo a
count stopkey
tem a ver com o filter
operação na linha 4 do plano "ruim". Você pode ver isso mais claramente se repetir o exemplo acima, mas sem particionamento. Isso lhe dá os seguintes planos:
----------------------------------------
| 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'))
Como você pode ver, há um
filter
extra operação quando você usa variáveis de ligação que aparecem antes da sort order by stopkey
. Isso acontece depois de acessar o índice. Isso está verificando se os valores das variáveis permitirão que os dados sejam retornados (a primeira variável em seu meio na verdade tem um valor menor que a segunda). Isso não é necessário ao usar literais porque o otimizador já sabe que 50 é menor que 100 (neste caso). No entanto, ele não sabe se :a é menor que :b no momento da análise. Por que exatamente isso eu não sei. Pode ser um design intencional da Oracle - não adianta fazer a verificação de stopkey se os valores definidos para as variáveis resultarem em zero linhas - ou apenas um descuido.