Eu tenho um procedimento armazenado que leva coleção de objetos. ( TABLE OF MyCustomType
). Dentro do procedimento estou tentando juntar esse parâmetro com tabelas reais.
Por exemplo, algo como
create or replace TYPE MYTYPE AS OBJECT (....);
CREATE OR REPLACE TYPE LIST_OF_MYTYPE AS TABLE OF MYTYPE;
CREATE PROCEDURE FOO(my_table LIST_OF_MYTYPE ,....) AS
CURSOR cur_read_data IS
SELECT a.col1, b.col2, b.col3
FROM
TABLE(FOO.my_table) a
INNER JOIN existing_table b ON (b.existing_table_id = a.existing_table_id)
--b.existing_table_id - primary key supported by unique index
ORDER BY a.existing_table_id
;
BEGIN
FOR record_info in cur_read_data
LOOP
......
END LOOP;
END;
Funciona, mas tenho um problema de desempenho. O parâmetro de coleção passado para o procedimento não possui muitos elementos; mesmo que tenha apenas um elemento, o plano de execução envolve a varredura completa da tabela de existing_table
.
No caso de 1 elemento, mudar INNER JOIN existing_table b ON (b.existing_table_id = a.existing_table_id)
para INNER JOIN existing_table b ON (b.existing_table_id =FOO.my_table(1).existing_table_id )
faz uma grande diferença - a consulta usa "INDEX UNIQUE SCAN" como eu esperava para a consulta inicial. Eu tentei até dicas de consulta (ordenadas, iniciais) sem resultados ...
Mesmo que o número de elementos na coleção em meu aplicativo esteja entre 1 e 5 e seja possível escrever 5 versões diferentes de um procedimento, gostaria de saber se é possível fazê-lo funcionar conforme o esperado.
Para testar também fiz
CURSOR cur_read_data IS
SELECT a.col1, b.col2, b.col3
FROM
(
SELECT 1 as existing_table_id, 'test 1' as col1 FROM DUAL
UNION
SELECT 2 as existing_table_id, 'test 2' as col1 FROM DUAL
UNION
SELECT 3 as existing_table_id, 'test 3' as col1 FROM DUAL
)
a
INNER JOIN existing_table b ON (b.existing_table_id = a.existing_table_id)
--b.existing_table_id - primary key supported by unique index
ORDER BY a.existing_table_id
Dessa forma também funciona como esperado, INDEX_UNIQUE_SCAN, não full scan...
Obrigado por suas respostas.
ATUALIZAÇÃO Surpreendentemente, mas reescrevendo-o para
CURSOR cur_read_data IS
WITH CTE1 AS (SELECT * FROM TABLE(FOO.my_table))
SELECT a.col1, b.col2, b.col3
FROM
CTE1 a
INNER JOIN existing_table b ON (b.existing_table_id = a.existing_table_id
AND b.existing_table_id IN (SELECT existing_table_id FROM CTE1))
ORDER BY a.existing_table_id
custo reduzido cerca de 30 vezes (1200 versão original vs 38 com WITH
) o que é aceitável, mas ainda não tenho ideia de por que isso ajudou ...
outra atualização
A análise V$SQLSTATS
revela que para a primeira versão do cursor ele evita DISK READS a qualquer custo (0), DIRECT WRITES também é 0; usar de WITH
alguma forma altera o plano de execução, resultando em grande melhoria no TEMPO da CPU, que supera o aumento de LEITURAS DE DISCO (1) ...
Um dos problemas com o uso de coleções em SQL é que o otimizador não consegue adivinhar quantos elementos a coleção possui. O padrão é assumir que a coleção tem alguns milhares de elementos (quero dizer 4k elementos, mas não apostaria nisso). Se isso for aproximadamente 1.000 vezes mais elementos do que você tem em sua coleção real, isso certamente fará com que o otimizador faça escolhas ruins sobre o plano de consulta.
A melhor maneira de aliviar esse problema é usar a
CARDINALITY
dica.informa ao otimizador para assumir que a coleção com alias
a
tem apenas 4 elementos, o que deve fazer com que ele escolha um plano de consulta mais apropriado.