Pergunta

A situação

Tenho alguns problemas com meu plano de execução de consulta para uma consulta de tamanho médio sobre uma grande quantidade de dados no Oracle 11.2.0.2.0.Para acelerar as coisas, introduzi um filtro de intervalo que faz mais ou menos assim:

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((org_from IS NULL) OR (org_from <= org.no))
   AND ((org_to   IS NULL) OR (org_to   >= org.no)))
  -- [...]

Como você pode ver, quero restringir o JOIN de organisations usando um intervalo opcional de números de organização.O código do cliente pode ligar DO_STUFF com (supostamente rápido) ou sem (muito lento) a restrição.

O problema

O problema é que o PL/SQL criará variáveis ​​de ligação para os itens acima org_from e org_to parâmetros, que é o que eu esperaria na maioria dos casos:

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((:B1 IS NULL) OR (:B1 <= org.no))
   AND ((:B2 IS NULL) OR (:B2 >= org.no)))
  -- [...]

A solução alternativa

Somente neste caso, medi que o plano de execução da consulta é muito melhor quando apenas incorporo os valores, ou seja,quando a consulta executada pelo Oracle é na verdade algo como

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))
  -- [...]

Por "muito", quero dizer 5 a 10 vezes mais rápido.Observe que a consulta é executada muito raramente, ou seja,uma vez por mês.Portanto, não preciso armazenar em cache o plano de execução.

Minhas perguntas

  • Como posso incorporar valores em PL/SQL?Eu sei sobre EXECUTAR IMEDIATAMENTE, mas eu preferiria que o PL/SQL compilasse minha consulta e não fizesse concatenação de strings.

  • Acabei de medir algo que aconteceu por coincidência ou posso assumir que incorporar variáveis ​​é realmente melhor (neste caso)?A razão pela qual pergunto é porque acho que as variáveis ​​de ligação forçam o Oracle a criar um em geral plano de execução, enquanto os valores embutidos permitiriam a análise colunas muito específicas e estatísticas de índice.Então posso imaginar que isso não seja apenas uma coincidência.

  • Estou esquecendo de algo?Talvez haja uma maneira totalmente diferente de obter melhorias no plano de execução de consultas, além do inlining variável (observe que também tentei algumas dicas, mas não sou um especialista nesse campo)?

Foi útil?

Solução

Em um de seus comentários você disse:

"Também verifiquei vários valores de ligação.Com variáveis ​​de ligação, recebo algumas varreduras completas, enquanto com valores codificados, o plano parece muito melhor. "

Existem dois caminhos.Se você passar NULL para os parâmetros, estará selecionando todos os registros.Nessas circunstâncias, uma varredura completa da tabela é a maneira mais eficiente de recuperar dados.Se você passar valores, as leituras indexadas poderão ser mais eficientes, porque você estará selecionando apenas um pequeno subconjunto de informações.

Ao formular a consulta usando variáveis ​​de ligação, o otimizador precisa tomar uma decisão:deveria presumir que na maioria das vezes você passará valores ou nulos?Difícil.Então veja de outra forma:é mais ineficiente fazer uma varredura completa da tabela quando você só precisa selecionar um subconjunto de registros ou fazer leituras indexadas quando precisa selecionar todos os registros?

Parece que o otimizador optou por varreduras completas de tabela como sendo a operação menos ineficiente para cobrir todas as eventualidades.

Considerando que quando você codifica os valores, o Otimizador sabe imediatamente que 10 IS NULL avalia como FALSE e, portanto, pode avaliar os méritos do uso de leituras indexadas para encontrar os registros do subconjunto desejado.


Então o que fazer?Como você disse, esta consulta é executada apenas uma vez por mês, acho que seria necessária apenas uma pequena alteração nos processos de negócios para ter consultas separadas:um para todas as organizações e outro para um subconjunto de organizações.


"Btw, removendo a cláusula :R1 IS NULL não altera o plano de execução muito, o que me deixa com o outro lado da condição OR, :R1 <= org.no onde NULL não faria sentido enfim, como org.no NÃO é NULO"

Ok, então o problema é que você tem um par de variáveis ​​de ligação que especificam um intervalo.Dependendo da distribuição dos valores, diferentes intervalos podem atender a diferentes planos de execução.Ou seja, esse intervalo (provavelmente) seria adequado para uma varredura de intervalo indexado...

WHERE org.id BETWEEN 10 AND 11

... enquanto isso provavelmente será mais adequado para uma varredura completa da tabela ...

WHERE org.id BETWEEN 10 AND 1199999

É aí que entra em jogo o Bind Variable Peeking.

(dependendo da distribuição de valores, claro).

Outras dicas

Como os planos de consulta são consistentemente diferentes, isso implica que as estimativas de cardinalidade do otimizador estão erradas por algum motivo.Você pode confirmar a partir dos planos de consulta que o otimizador espera que as condições sejam insuficientemente seletivas quando variáveis ​​de ligação são usadas?Como você está usando o 11.2, o Oracle deveria estar usando compartilhamento adaptativo de cursor portanto, não deve ser um problema de visualização de variáveis ​​de ligação (supondo que você esteja chamando a versão com variáveis ​​de ligação muitas vezes com diferentes NO valores em seus testes.

As estimativas de cardinalidade no bom plano estão realmente corretas?Eu sei que você disse que as estatísticas sobre o NO coluna são precisas, mas eu suspeitaria de um histograma perdido que pode não ser atualizado pelo seu processo regular de coleta de estatísticas, por exemplo.

Você sempre pode usar uma dica na consulta para forçar o uso de um índice específico (embora use um estrutura armazenada ou estabilidade do plano otimizador seria preferível do ponto de vista da manutenção a longo prazo).Qualquer uma dessas opções seria preferível a recorrer ao SQL dinâmico.

Um teste adicional a ser tentado, entretanto, seria substituir a sintaxe de junção do SQL 99 pela sintaxe antiga do Oracle, ou seja,

SELECT <<something>>
  FROM <<some other table>> cust,
       organization org
 WHERE cust.org_id = org.id
   AND (    ((org_from IS NULL) OR (org_from <= org.no)) 
        AND ((org_to   IS NULL) OR (org_to   >= org.no)))

Obviamente, isso não deve mudar nada, mas houve problemas de analisador com a sintaxe do SQL 99, então isso é algo a ser verificado.

Cheira a Vincular Espiar, mas estou apenas no Oracle 10, então não posso afirmar que o mesmo problema existe no 11.

Isso se parece muito com a necessidade de Compartilhamento Adaptativo de Cursor, combinado com a estabilidade do SQLPlan.Acho que o que está acontecendo é que capture_sql_plan_baselines parameter is true.E o mesmo para use_sql_plan_baselines.Se isso for verdade, o seguinte está acontecendo:

  1. Na primeira vez que uma consulta é iniciada, ela é analisada e obtém um novo plano.
  2. Na segunda vez, esse plano é armazenado em sql_plan_baselines como um plano aceito.
  3. Todas as execuções seguintes desta consulta usam este plano, independentemente de quais sejam as variáveis ​​de ligação.

Se o Compartilhamento Adaptativo do Cursor já estiver ativo, o otimizador irá gerar um plano novo/melhor, armazená-lo em sql_plan_baselines, mas não será capaz de usá-lo, até que alguém aceite este plano mais novo como um plano alternativo aceitável.Verificar dba_sql_plan_baselines e veja se sua consulta tem entradas com accepted = 'NO' and verified = nullVocê pode usar dbms_spm.evolve evoluir o novo plano e tê-lo automaticamente aceito se o desempenho do plano for pelo menos 1,5 vezes melhor do que sem o novo plano.

Eu espero que isso ajude.

Eu adicionei isso como um comentário, mas vou oferecer aqui também.Espero que isso não seja muito simplista e, olhando as respostas detalhadas, posso estar entendendo mal o problema exato, mas de qualquer maneira...

Parece que a tabela da sua organização possui a coluna no (org.no) definida como um número.No seu exemplo codificado, você usa números para fazer as comparações.

JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))

No seu procedimento, você está passando varchar2:

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

Então, para comparar varchar2 com número, Oracle terá que fazer as conversões, então isso pode causar verificações completas.

Solução: mude proc para passar em números

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top