Portanto, tivemos um processo de longa duração causando problemas esta manhã (30 segundos + tempo de execução). Decidimos verificar se a detecção de parâmetros era a culpada. Portanto, reescrevemos o proc e definimos os parâmetros de entrada para variáveis para impedir a detecção de parâmetros. Uma abordagem testada/verdadeira. Bam, o tempo de consulta melhorou (menos de 1 segundo). Ao olhar para o plano de consulta, as melhorias foram encontradas em um índice que o original não estava usando.
Apenas para verificar se não obtivemos um falso positivo, fizemos um dbcc freeproccache no proc original e rodamos novamente para ver se os resultados aprimorados seriam os mesmos. Mas, para nossa surpresa, o proc original ainda rodava devagar. Tentamos novamente com um WITH RECOMPILE, ainda lento (tentamos uma recompilação na chamada para o proc e dentro do próprio proc). Até reiniciamos o servidor (caixa de desenvolvimento obviamente).
Então, minha pergunta é esta... como o sniffing de parâmetro pode ser o culpado quando obtemos a mesma consulta lenta em um cache de plano vazio... não deveria haver nenhum parâmetro para snif???
Em vez disso, estamos sendo afetados pelas estatísticas da tabela não relacionadas ao cache do plano. E se assim for, por que definir os parâmetros de entrada para variáveis ajudaria?
Em testes adicionais, também descobrimos que a inserção de OPTION (OPTIMIZE FOR UNKNOWN) nas partes internas do proc DID obteve o plano aprimorado esperado.
Então, alguns de vocês mais espertos do que eu, podem dar algumas pistas sobre o que está acontecendo nos bastidores para produzir esse tipo de resultado?
Em outra observação, o plano lento também é abortado antecipadamente com razão GoodEnoughPlanFound
, enquanto o plano rápido não tem motivo de interrupção antecipada no plano real.
Resumindo
- Criando variáveis a partir de parâmetros de entrada (1 seg)
- com recompilação (30+ seg)
- dbcc freeproccache (mais de 30 segundos)
- OPÇÃO (OPTIMIZE FOR UKNOWN) (1 seg)
ATUALIZAR:
Veja o plano de execução lenta aqui: https://www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml
Veja o plano de execução rápida aqui: https://www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml
Nota: tabela, esquema, nomes de objetos alterados por motivos de segurança.
a consulta é
A tabela contém 103.129.000 linhas.
O plano rápido pesquisa por ClientId com um predicado residual na data, mas precisa fazer 96 pesquisas para recuperar o arquivo
Amount
. A<ParameterList>
seção no plano é a seguinte.O plano lento procura por data e tem pesquisas para avaliar o predicado residual em ClientId e para recuperar o valor (Estimated 1 vs Real 7.388.383). a
<ParameterList>
seção éNeste segundo caso, o não
ParameterCompiledValue
está vazio. O SQL Server detectou com êxito os valores usados na consulta.O livro "SQL Server 2005 Practical Troubleshooting" fala sobre o uso de variáveis locais
A partir de um teste rápido, o comportamento descrito acima ainda é o mesmo em 2008 e 2012 e as variáveis não são detectadas para compilação adiada, mas apenas quando uma
OPTION RECOMPILE
dica explícita é usada.Apesar da compilação adiada, a variável não é detectada e a contagem de linhas estimada é imprecisa
Portanto, presumo que o plano lento esteja relacionado a uma versão parametrizada da consulta.
O
ParameterCompiledValue
é igual aParameterRuntimeValue
para todos os parâmetros, portanto, isso não é uma detecção de parâmetro típica (em que o plano foi compilado para um conjunto de valores e executado para outro conjunto de valores).O problema é que o plano compilado para os valores de parâmetro corretos é inadequado.
Você provavelmente está enfrentando o problema com as datas ascendentes descritas aqui e aqui . Para uma tabela com 100 milhões de linhas, você precisa inserir (ou modificar) 20 milhões antes que o SQL Server atualize automaticamente as estatísticas para você. Parece que da última vez que eles foram atualizados, zero linhas corresponderam ao intervalo de datas na consulta, mas agora 7 milhões correspondem.
Você pode agendar atualizações de estatísticas mais frequentes, considerar sinalizadores de rastreamento
2389 - 90
ou usarOPTIMIZE FOR UKNOWN
apenas suposições, em vez de poder usar as estatísticas atualmente enganosas nadatetime
coluna.Isso pode não ser necessário na próxima versão do SQL Server (após 2012). Um item Connect relacionado contém a resposta intrigante
Essa melhoria de 2014 é analisada por Benjamin Nevarez no final do artigo:
Uma primeira olhada no novo estimador de cardinalidade do SQL Server .
Parece que o novo estimador de cardinalidade retrocederá e usará a densidade média neste caso, em vez de fornecer a estimativa de 1 linha.
Alguns detalhes adicionais sobre o estimador de cardinalidade de 2014 e o problema de chave ascendente aqui:
Nova funcionalidade no SQL Server 2014 – Parte 2 – Nova estimativa de cardinalidade
Quando o SQL Server compila uma consulta contendo valores de parâmetro, ele detecta os valores específicos desses parâmetros para estimativa de cardinalidade (contagem de linhas). No seu caso, os valores específicos de
@BeginDate
,@EndDate
e@ClientID
são usados ao escolher um plano de execução. Você pode encontrar mais detalhes sobre a detecção de parâmetros aqui e aqui . Estou fornecendo esses links de plano de fundo porque a pergunta acima me faz pensar que o conceito é mal compreendido no momento - sempre há valores de parâmetro para farejar quando um plano é compilado.De qualquer forma, isso não vem ao caso, porque a detecção de parâmetros não é o problema aqui, como Martin Smith apontou. No momento em que a consulta lenta foi compilada, as estatísticas indicaram que não havia linhas para os valores detectados de
@BeginDate
e@EndDate
:Os valores detectados são muito recentes, sugerindo o problema da chave ascendente que Martin menciona. Como se estima que a busca de índice nas datas retorne apenas uma única linha, o otimizador escolhe um plano que envia o predicado
ClientID
para o operador Key Lookup como um resíduo.A estimativa de linha única também é o motivo pelo qual o otimizador para de procurar planos melhores, retornando uma mensagem Good Enough Plan Found. O custo total estimado do plano lento com a estimativa de linha única é de apenas 0,013136 unidades de custo, portanto, não faz sentido tentar encontrar algo melhor. Exceto, é claro, que a busca realmente retorna 7.388.383 linhas em vez de uma, causando o mesmo número de pesquisas de chave.
As estatísticas podem ser difíceis de manter atualizadas e úteis em tabelas grandes e o particionamento apresenta seus próprios desafios a esse respeito. Eu mesmo não tive sucesso particular com sinalizadores de rastreamento 2389 e 2390, mas você pode testá-los. Compilações mais recentes do SQL Server (R2 SP1 e posterior) têm atualizações de estatísticas dinâmicas disponíveis, mas essas atualizações de estatísticas por partição ainda não foram implementadas. Enquanto isso, você pode agendar uma atualização manual de estatísticas sempre que fizer alterações significativas nesta tabela.
Para esta consulta específica, eu pensaria em implementar o índice sugerido pelo otimizador durante a compilação do plano de consulta rápida:
O índice deve ser alinhado à partição, com uma
ON PartitionSchemeName (PostedDate)
cláusula, mas o ponto é que fornecer um caminho de acesso a dados obviamente melhor ajudará o otimizador a evitar escolhas de plano ruins, sem recorrer aOPTIMIZE FOR UNKNOWN
dicas ou soluções antiquadas como o uso de variáveis locais.Com o índice aprimorado, a pesquisa de chave para recuperar a
Amount
coluna será eliminada, o processador de consulta ainda pode executar a eliminação de partição dinâmica e usar uma busca para localizar oClientID
intervalo de datas e específico.Eu tive exatamente o mesmo problema em que um procedimento armazenado ficou lento
OPTIMIZE FOR UNKNOWN
eRECOMPILE
as dicas de consulta resolveram a lentidão e aceleraram o tempo de execução. No entanto, os dois métodos a seguir não afetaram a lentidão do procedimento armazenado: (i) Limpando o cache (ii) usando WITH RECOMPILE. Então, assim como você disse, não foi realmente um sniffing de parâmetro.Os sinalizadores de rastreamento 2389 e 2390 também não ajudaram. Apenas atualizar as estatísticas (
EXEC sp_updatestats
) fez isso por mim.