Temos uma consulta que estava obtendo baixo desempenho. A raiz do problema poderia ser reproduzida usando uma consulta simples acessando apenas um índice para recuperar uma coluna (a coluna indexada) de oito linhas.
A tabela não tinha estatísticas, mas o índice tinha. Reunir novas estatísticas sobre o índice não mudou o plano, mas reunir estatísticas sobre a mesa sim. Meu entendimento era que uma consulta que pudesse ser satisfeita usando apenas o índice não precisaria acessar a tabela, portanto meu modelo mental era que as estatísticas da tabela não importariam neste caso, mas a experiência parece indicar o contrário.
Tanto o plano de explicação quanto o plano de rastreamento automático mostram apenas o acesso ao índice, mas quando as estatísticas da tabela não estão presentes, há custos e cardinalidade significativamente maiores. O autotrace mostra maior CPU, DB Time e Consistent Gets. Ainda não tentei rastreá-lo, mas posso reproduzi-lo criando/soltando estatísticas na tabela conforme mostrado abaixo. Alguém pode explicar esse comportamento?
set serveroutput on
DECLARE
numr NUMBER;
numb NUMBER;
avgr NUMBER;
nrow NUMBER;
nblk NUMBER;
numd NUMBER;
avgl NUMBER;
avgd NUMBER;
cfac NUMBER;
ilvl NUMBER;
gues NUMBER;
BEGIN
--Gather Stats.
dbms_stats.Gather_table_Stats(USER,'RESULTS');
--Gather Index Stats.
dbms_stats.Gather_index_Stats(USER,'I1');
--Show Index Stats.
dbms_stats.get_index_stats(USER, 'I1', NULL, NULL, NULL, nrow, nblk
, numd, avgl, avgd, cfac, ilvl, NULL, gues);
dbms_output.put_line('Number of rows: ' || TO_CHAR(nrow));
dbms_output.put_line('Number of blocks: ' || TO_CHAR(nblk));
dbms_output.put_line('Distinct keys: ' || TO_CHAR(numd));
dbms_output.put_line('Avg leaf blocks/key: ' || TO_CHAR(avgl));
dbms_output.put_line('Avg data blocks/key: ' || TO_CHAR(avgd));
dbms_output.put_line('Clustering factor: ' || TO_CHAR(cfac));
dbms_output.put_line('Index level: ' || TO_CHAR(ilvl));
dbms_output.put_line('IOT guess quality: ' || TO_CHAR(gues));
delete from plan_table;
END;
/
EXPLAIN PLAN FOR SELECT rsample_id FROM results
WHERE rsample_id = '0555103360';
SELECT cost, substr(lpad(' ', level-1) || operation || ' (' || options
|| ')',1,50 ) "Operation", object_name "Object"
FROM plan_table START WITH ID = 0 CONNECT BY PRIOR id=parent_id;
DECLARE
nrow NUMBER;
nblk NUMBER;
numd NUMBER;
avgl NUMBER;
avgd NUMBER;
cfac NUMBER;
ilvl NUMBER;
gues NUMBER;
BEGIN
--Delete Stats.
dbms_stats.delete_table_stats(USER,'RESULTS');
--Gather Index Stats.
dbms_stats.Gather_index_Stats('LRIFFEL','I1');
--Show Index Stats.
dbms_stats.get_index_stats(USER, 'I1', NULL, NULL, NULL, nrow, nblk
, numd, avgl, avgd, cfac, ilvl, NULL, gues);
dbms_output.put_line('Number of rows: ' || TO_CHAR(nrow));
dbms_output.put_line('Number of blocks: ' || TO_CHAR(nblk));
dbms_output.put_line('Distinct keys: ' || TO_CHAR(numd));
dbms_output.put_line('Avg leaf blocks/key: ' || TO_CHAR(avgl));
dbms_output.put_line('Avg data blocks/key: ' || TO_CHAR(avgd));
dbms_output.put_line('Clustering factor: ' || TO_CHAR(cfac));
dbms_output.put_line('Index level: ' || TO_CHAR(ilvl));
dbms_output.put_line('IOT guess quality: ' || TO_CHAR(gues));
delete from plan_table;
END;
/
EXPLAIN PLAN FOR SELECT rsample_id FROM results
WHERE rsample_id = '0555103360';
SELECT cost, substr(lpad(' ', level-1) || operation || ' (' || options
|| ')',1,50 ) "Operation", object_name "Object"
FROM plan_table START WITH ID = 0 CONNECT BY PRIOR id=parent_id;
Isso teve a seguinte saída (modificada para caber):
anonymous block completed
Number of rows: 125226611
Number of blocks: 381090
Distinct keys: 5778886
Avg leaf blocks/key: 1
Avg data blocks/key: 3
Clustering factor: 19792294
Index level: 3
IOT guess quality:
plan FOR succeeded.
COST Operation Object
----- --------------------- ------
4 SELECT STATEMENT()
4 INDEX (RANGE SCAN) I1
anonymous block completed
Number of rows: 119034073
Number of blocks: 362402
Distinct keys: 5353024
Avg leaf blocks/key: 1
Avg data blocks/key: 3
Clustering factor: 18852918
Index level: 3
IOT guess quality:
plan FOR succeeded.
COST Operation Object
----- --------------------- ------
9 SELECT STATEMENT()
9 INDEX (RANGE SCAN) I1
Depois de criar isso, notei que as estatísticas do índice eram diferentes para cada execução, embora nada devesse mudar na tabela e as estatísticas do índice fossem reunidas novamente em cada execução. Minha teoria agora é que algo nas estatísticas do índice é retido ao reunir as estatísticas da tabela com a opção cascata, mesmo quando as estatísticas do índice são reunidas novamente.
A granularidade é definida como AUTO e Cascade é definida como AUTO_CASCADE.
Eu imagino que o CBO de alguma forma calcula a aproximação das estatísticas da tabela a partir das estatísticas do índice ou usa alguma regra prática.
Estatísticas diferentes podem ser causadas por