Eu tenho uma tabela com 250K linhas no meu banco de dados de teste. (Existem algumas centenas de milhões em produção, podemos observar o mesmo problema.) A tabela possui um identificador de seqüência de caracteres nvarchar2 (50), não nulo, com um índice exclusivo (não é o PK).
Os identificadores são compostos de uma primeira parte que possui 8 valores diferentes no meu banco de dados de teste (e cerca de mil em produção), depois um sinal @ e, finalmente, um número com 1 a 6 dígitos. Por exemplo, pode haver 50 mil linhas que começam com 'ABCD_BGX1741F_2006_13_20110808.xml @' e é seguido por 50 mil números diferentes.
Quando eu procuro uma única linha com base em seu identificador, a cardinalidade é estimada em 1, o custo é muito baixo e funciona bem. Quando eu procuro por mais de uma linha com vários identificadores em uma expressão IN ou OR, as estimativas para o índice estão completamente erradas, portanto, uma varredura de tabela completa é usada. Se eu forçar o índice com uma dica, é muito rápido, a verificação completa da tabela é realmente executada uma ordem de magnitude mais lenta (e muito mais lenta na produção). Portanto, é um problema do otimizador.
Como teste, dupliquei a tabela (no mesmo esquema + espaço de tabela) com exatamente o mesmo DDL e exatamente o mesmo conteúdo. Recriei o índice exclusivo na primeira tabela para uma boa medida e criei exatamente o mesmo índice na tabela de clones. Eu fiz um DBMS_STATS.GATHER_SCHEMA_STATS('schemaname',estimate_percent=>100,cascade=>true);
. Você pode até ver que os nomes dos índices são consecutivos. Portanto, agora a única diferença entre as duas tabelas é que a primeira foi carregada em ordem aleatória por um longo período de tempo, com blocos espalhados no disco (em um espaço de tabela juntamente com várias outras grandes tabelas), e a segunda foi carregada como um lote INSERIR-SELECIONAR. Fora isso, não consigo imaginar nenhuma diferença. (A tabela original foi reduzida desde a última grande exclusão e não houve uma única exclusão depois disso.)
Aqui estão os planos de consulta para a tabela de doentes e clones (as strings sob o pincel preto são as mesmas em toda a imagem e também sob o pincel cinza.):
(Neste exemplo, existem 1867 linhas que começam com o identificador escovado em preto. Uma consulta de 2 linhas produz uma cardinalidade de 1867 * 2, enquanto uma consulta de 3 linhas produz uma cardinalidade de 1867 * 3, etc. por coincidência, a Oracle parece não se importar com o fim dos identificadores.)
O que poderia causar esse comportamento? Obviamente, seria muito caro recriar a tabela em produção.
USER_TABLES: http://i.stack.imgur.com/nDWze.jpg USER_INDEXES: http://i.stack.imgur.com/DG9um.jpg Eu só mudou o nome do esquema e tabela. Você pode ver que os nomes da tabela e do índice são os mesmos da captura de tela do plano de consulta.
fonte
in
), não? Eu acho que o CBO faz a suposição de cardinalidade 1, mas apenas no caso mais simples. Suponho que você possa solucionar a coisa toda usando um valor grande,UNION ALL
mas pode haver outros motivos para não fazer isso e JL menciona outras soluções possíveis na postagem do blog vinculado.method_opt=>'for all indexed columns'
?Eu encontrei a solução! É tão bonito e eu realmente aprendi MUITO sobre o Oracle.
Em uma palavra: histogramas.
Comecei a ler muito sobre como o CBO da Oracle funciona e me deparei com histogramas. Como não entendi completamente, dei uma olhada na tabela USER_HISTOGRAMS e voilá. Havia várias linhas para a mesa doente e praticamente nada para a mesa clonada. Para a tabela doente, havia uma linha para cada uma das oito partes iniciais identificadoras diferentes. E esta é a chave: eles foram cortados em 32 caracteres, antes do sinal @. Como eu disse, a primeira parte das teclas é altamente repetitiva, elas se tornam diferentes após o sinal @.
Parece que os histogramas podem ser mais poderosos do que o simples fato de um índice único sempre ter uma cardinalidade de 0 ou 1 para um determinado valor. Quando eu estava consultando mais de duas linhas, a Oracle olhou para o histograma, e pensou que poderia haver dezenas de milhares de valores para essa parte inicial do identificador e tirou o CBO do caminho.
Apaguei os histogramas dessa coluna na tabela antiga e o problema desapareceu!
Mais informações: https://blogs.oracle.com/optimizer/entry/how_do_i_drop_an_existing_histogram_on_a_column_and_stop_the_auto_stats_gathering_job_from_creating
fonte
Enviei um email para Jonathan Lewis sobre isso e recebi uma resposta muito útil:
Eu recomendo a leitura dos posts que ele vincula, eles descrevem em detalhes a limitação dos histogramas nos quais você está executando, por exemplo:
fonte