OPÇÃO (RECOMPILE) é sempre mais rápida; Por quê?

169

Eu encontrei uma situação estranha em que o acréscimo OPTION (RECOMPILE)à minha consulta faz com que ela seja executada em meio segundo, enquanto a omissão faz com que a consulta demore mais de cinco minutos.

Este é o caso quando a consulta é executada no Query Analyzer ou no meu programa C # via SqlCommand.ExecuteReader(). Ligar (ou não ligar) DBCC FREEPROCCACHEou DBCC dropcleanbuffersnão faz diferença; Os resultados da consulta sempre são retornados instantaneamente com OPTION (RECOMPILE)mais de cinco minutos sem ela. A consulta é sempre chamada com os mesmos parâmetros [para fins deste teste].

Estou usando o SQL Server 2008.

Estou bastante confortável em escrever SQL, mas nunca usei um OPTIONcomando em uma consulta antes e não estava familiarizado com todo o conceito de caches de plano até verificar as postagens neste fórum. Meu entendimento das postagens é que OPTION (RECOMPILE)é uma operação cara. Aparentemente, cria uma nova estratégia de pesquisa para a consulta. Então, por que as consultas subsequentes que omitem o OPTION (RECOMPILE)são tão lentas? As consultas subsequentes não deveriam usar a estratégia de pesquisa que foi computada na chamada anterior, que incluía a dica de recompilação?

É altamente incomum ter uma consulta que exija uma dica de recompilação em cada chamada?

Desculpem a pergunta de nível de entrada, mas não consigo realmente entender isso.

UPDATE: me pediram para postar a consulta ...

select acctNo,min(date) earliestDate 
from( 
    select acctNo,tradeDate as date 
    from datafeed_trans 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_money 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_jnl 
    where feedid=@feedID and feedDate=@feedDate 
)t1 
group by t1.acctNo
OPTION(RECOMPILE)

Ao executar o teste no Query Analyzer, acrescento as seguintes linhas:

declare @feedID int
select @feedID=20

declare @feedDate datetime
select @feedDate='1/2/2009'

Ao chamá-lo do meu programa C #, os parâmetros são passados ​​através da SqlCommand.Parameterspropriedade

Para os fins desta discussão, você pode assumir que os parâmetros nunca mudam, para que possamos descartar o cheiro sub-ideal dos parâmetros como a causa.

Chad Decker
fonte
3
Quais são os parâmetros para a consulta? Confira este artigo. blogs.msdn.com/b/turgays/archive/2013/09/10/… Basicamente, o SQL tenta gerar o plano de consulta com base nos parâmetros quando o processo é compilado pela primeira vez. Ele pode gerar um plano que não é o ideal quando você começar a passar parâmetros diferentes, possivelmente mais realistas
Sparky
3
A consulta é concisa o suficiente para listar aqui? Acho Sparky está correto e que provavelmente está relacionado ao parâmetro sniffing, eu tive um problema semelhante que confundiu o inferno fora de mim até ler este excelente artigo: sommarskog.se/query-plan-mysteries.html
Chris
1
Mas, neste caso (pelo bem deste teste), estou sempre passando nos mesmos parâmetros. Nenhum outro aplicativo conseguiu entrar e chamar a consulta usando outros parâmetros. Obrigado pelos artigos. Irá rever.
precisa saber é o seguinte
2
Isso pode acontecer porque ele fareja os valores dos parâmetros e variáveis ​​ou porque faz maiores simplificações. Exemplos de simplificações maiores seriam colapso X = @X OR @X IS NULLde X=@Xe realizando uma busca Veja aqui ou empurrar predicados ainda mais para baixo contra uma exibição com funções da janela
Martin Smith
3
Após sua edição, o exemplo do Query Analyzer usa variáveis, não parâmetros. o valor desses nunca é farejado, exceto com RECOMPILE. De qualquer forma, capture os planos de execução e observe as diferenças.
Martin Smith

Respostas:

157

Há momentos em que o uso OPTION(RECOMPILE)faz sentido. Na minha experiência, a única vez que essa é uma opção viável é quando você está usando SQL dinâmico. Antes de explorar se isso faz sentido na sua situação, recomendo reconstruir suas estatísticas. Isso pode ser feito executando o seguinte:

EXEC sp_updatestats

E depois recrie seu plano de execução. Isso garantirá que, quando seu plano de execução for criado, ele usará as informações mais recentes.

A adição de OPTION(RECOMPILE)reconstruções do plano de execução sempre que sua consulta é executada. Nunca ouvi isso como descrito, creates a new lookup strategymas talvez estejamos apenas usando termos diferentes para a mesma coisa.

Quando um procedimento armazenado é criado (suspeito que você esteja chamando ad-hoc sql do .NET, mas se você estiver usando uma consulta parametrizada, isso acabará sendo uma chamada de procedimento armazenado ), o SQL Server tentará determinar o plano de execução mais eficaz para essa consulta com base nos dados do seu banco de dados e nos parâmetros passados ​​( detecção de parâmetros ) e, em seguida, armazena em cache esse plano. Isso significa que, se você criar a consulta em que há 10 registros no banco de dados e executá-la quando houver 100.000.000 de registros, o plano de execução em cache poderá não ser mais eficaz.

Em resumo - não vejo nenhum motivo que OPTION(RECOMPILE)seja um benefício aqui. Eu suspeito que você só precisa atualizar suas estatísticas e seu plano de execução. A reconstrução de estatísticas pode ser uma parte essencial do trabalho do DBA, dependendo da sua situação. Se você ainda tiver problemas após atualizar suas estatísticas, sugiro postar os dois planos de execução.

E para responder sua pergunta - sim, eu diria que é altamente incomum que sua melhor opção recompile o plano de execução toda vez que você executar a consulta.

Abe Miessler
fonte
22
Sim, sp_updatestats fez o truque. Você acertou na cabeça quando mencionou uma consulta executada inicialmente em uma tabela com 10 registros e agora a tabela possui milhões de registros. Esse foi exatamente o meu caso. Não mencionei isso no post porque não achei que isso importasse. Coisas fascinantes. Obrigado novamente.
Chad Decker
3
É a única maneira que encontrei para trabalhar com variáveis ​​de tabela, porque o SQL sempre pensa que há uma única linha. Quando contém milhares de linhas, isso se torna um problema.
Alex Zhukovskiy
4
Um detalhe interessante: a atualização de estatísticas invalida implicitamente todos os planos em cache que usam essas estatísticas, mas apenas se as estatísticas forem realmente alteradas após a ação de atualização . Portanto, para tabelas somente leitura altamente distorcidas, parece que uma explícita OPTION (RECOMPILE)pode ser a única solução.
Groo
141

Muitas vezes, quando há uma diferença drástica de execução para execução de uma consulta, acho que essa é uma das cinco questões.

  1. ESTATISTICAS- As estatísticas estão desatualizadas. Um banco de dados armazena estatísticas sobre o intervalo e a distribuição dos tipos de valores em várias colunas em tabelas e índices. Isso ajuda o mecanismo de consulta a desenvolver um "Plano" de ataque sobre como realizará a consulta, por exemplo, o tipo de método que ele usará para corresponder chaves entre tabelas usando um hash ou examinando o conjunto inteiro. Você pode chamar Estatísticas de atualização em todo o banco de dados ou apenas em determinadas tabelas ou índices. Isso reduz a velocidade da consulta de uma execução para outra porque, quando as estatísticas estão desatualizadas, é provável que o plano de consulta não seja ideal para os dados recém-inseridos ou alterados para a mesma consulta (explicado mais adiante mais adiante). Pode não ser adequado atualizar as estatísticas imediatamente em um banco de dados de produção, pois haverá sobrecarga, lentidão e atraso, dependendo da quantidade de dados a serem amostrados. Você também pode optar por usar uma verificação completa ou amostragem para atualizar as estatísticas. Se você olhar para o Plano de Consulta, também poderá visualizar as estatísticas nos Índices em uso, usando o comandoDBCC SHOW_STATISTICS (nome da tabela, nome do índice) . Isso mostrará a distribuição e os intervalos das chaves que o plano de consulta está usando para basear sua abordagem.

  2. SNIFFING DE PARÂMETROS - O plano de consulta armazenado em cache não é ideal para os parâmetros específicos que você está passando, mesmo que a consulta em si não tenha sido alterada. Por exemplo, se você passar um parâmetro que recupera apenas 10 das 1.000.000 de linhas, o plano de consulta criado poderá usar uma Hash Join; no entanto, se o parâmetro que você passar usar 750.000 das 1.000.000 de linhas, o plano criado poderá ser um varredura de índice ou varredura de tabela. Em tal situação, você pode dizer à instrução SQL para usar a opção OPTION (RECOMPILE) ou um SP para usar WITH RECOMPILE. Dizer ao mecanismo que este é um "plano de uso único" e não usar um plano em cache que provavelmente não se aplica. Não existe uma regra sobre como tomar essa decisão, depende de saber como a consulta será usada pelos usuários.

  3. ÍNDICES - É possível que a consulta não tenha sido alterada, mas uma alteração em outro lugar, como a remoção de um índice muito útil, diminuiu a velocidade da consulta.

  4. LINHAS ALTERADAS - As linhas que você está consultando mudam drasticamente de uma chamada para outra. Normalmente, as estatísticas são atualizadas automaticamente nesses casos. No entanto, se você estiver criando SQL dinâmico ou chamando o SQL em um loop apertado, é possível que você esteja usando um Plano de Consulta desatualizado com base no número drástico de linhas ou estatísticas incorretas. Novamente, neste caso, OPTION (RECOMPILE) é útil.

  5. A LÓGICA É a Lógica, sua consulta não é mais eficiente, era boa para um pequeno número de linhas, mas não era mais escalável. Isso geralmente envolve uma análise mais aprofundada do plano de consulta. Por exemplo, você não pode mais fazer coisas em massa, mas precisa fazer partes em pedaços e fazer Commits menores, ou seu produto cruzado foi bom para um conjunto menor, mas agora ocupa CPU e memória à medida que aumenta, isso também pode ser verdade para usando DISTINCT, você está chamando uma função para cada linha, suas correspondências de teclas não usam um índice por causa da conversão de tipo CASTING ou NULLS ou funções ... Muitas possibilidades aqui.

Em geral, ao escrever uma consulta, você deve ter uma imagem mental de como certos dados são distribuídos em sua tabela. Uma coluna, por exemplo, pode ter um número uniformemente distribuído de valores diferentes, ou pode ser distorcida, 80% das vezes possui um conjunto específico de valores, independentemente de a distribuição variar com frequência ao longo do tempo ou ser estática. Isso lhe dará uma idéia melhor de como criar uma consulta eficiente. Mas também quando o desempenho da consulta de depuração tem uma base para construir uma hipótese de por que é lenta ou ineficiente.

CodeCowboyOrg
fonte
2
obrigada amigo Esta é uma informação excelente. Eu não teria conseguido entender sua resposta quando postei minha pergunta originalmente, mas agora faz todo o sentido para mim.
Chad Decker
3
O SNIFFING DE PARÂMETROS é de longe o maior problema para a minha existência. Eu nem sabia sobre esse comando até uma pergunta com falha na entrevista. Minha solução para o sniffing de parâmetros sempre foi o hash dos valores dos parâmetros e anexou "AND {hash} = {hash}" para que o sql fosse sempre diferente para valores diferentes. Um hack, mas funcionou.
Jeremy Boyd
27

Para adicionar à excelente lista (fornecida por @CodeCowboyOrg) de situações em que OPTION (RECOMPILE) pode ser muito útil,

  1. Variáveis ​​de tabela . Quando você estiver usando variáveis ​​de tabela, não haverá estatísticas pré-criadas para a variável de tabela, geralmente levando a grandes diferenças entre as linhas estimadas e reais no plano de consulta. O uso de OPTION (RECOMPILE) em consultas com variáveis ​​de tabela permite a geração de um plano de consulta com uma estimativa muito melhor dos números de linhas envolvidos. Eu tinha um uso particularmente crítico de uma variável de tabela que não podia ser usada e que eu abandonaria até adicionar OPTION (RECOMPILE). O tempo de execução passou de horas para apenas alguns minutos. Provavelmente isso é incomum, mas, de qualquer forma, se você estiver usando variáveis ​​de tabela e trabalhando na otimização, vale a pena ver se OPTION (RECOMPILE) faz a diferença.
DWright
fonte
1
Eu tenho uma consulta com 5 variáveis ​​de tabela. Na minha máquina, ele é executado por mais de meia hora. Na máquina do meu colega de trabalho, ele é executado em <1 segundo. As máquinas possuem hardware semelhante e a mesma versão do SQL Server. Se ambos adicionarmos OPTION (RECOMPILE), ele será executado em 2 segundos nas duas máquinas. Em todos os casos, o teste de execução é realizado no SSMS. O que poderia estar causando essa diferença?
Adam
1
Você pode comparar o plano de execução na sua máquina e na máquina de seus colegas sem Option (recompilar)? Isso pode mostrar a fonte da diferença.
DWright
1
para tabelas temporárias, é a mesma situação?
Muflix
1
@ muflix: boa pergunta. Não acredito que o efeito seja o mesmo para tabelas temporárias, pois elas têm estatísticas e o mecanismo deve fazer escolhas automáticas de recompilação, como para outras tabelas, acredito (mas não tenho certeza). Talvez alguém saiba com maior certeza.
DWright
2
As estatísticas nas tabelas temporárias não são atualizadas ou recompiladas automaticamente; portanto, o programador precisa fazer isso.
J. Michael Wuerth
1

As primeiras ações antes de executar as consultas é desfragmentar / reconstruir os índices e as estatísticas, caso contrário você estará perdendo seu tempo.

Você deve verificar o plano de execução para ver se ele é estável (é o mesmo quando você altera os parâmetros); caso contrário, pode ser necessário criar um índice de cobertura (neste caso para cada tabela) (sabendo que sistema você pode criar um que também é útil para outras consultas).

como um exemplo: criar índice idx01_datafeed_trans No datafeed_trans (feedid, feedDate) INCLUDE (acctNo, tradeDate)

se o plano estiver estável ou você puder estabilizá-lo, execute a sentença com sp_executesql ('sql sentença') para salvar e usar um plano de execução fixo.

se o plano for instável, você precisará usar uma instrução ad-hoc ou EXEC ('sentença sql') para avaliar e criar um plano de execução a cada vez. (ou um procedimento armazenado "com recompilação").

Espero que ajude.

Cristian Solervicéns
fonte
1

Necroing esta pergunta, mas há uma explicação que ninguém parece ter considerado.

ESTATÍSTICAS - As estatísticas não estão disponíveis ou são enganosas

Se todas as seguintes opções forem verdadeiras:

  1. As colunas feedid e feedDate provavelmente serão altamente correlacionadas (por exemplo, um ID de feed é mais específico que uma data de feed e o parâmetro date é uma informação redundante).
  2. Não há índice com ambas as colunas como colunas seqüenciais.
  3. Não há estatísticas criadas manualmente cobrindo essas duas colunas.

Em seguida, o servidor sql pode estar incorretamente assumindo que as colunas não estão correlacionadas, levando a estimativas de cardinalidade abaixo do esperado para aplicar restrições e um plano de execução ruim sendo selecionado. A correção nesse caso seria criar um objeto estatístico vinculando as duas colunas, o que não é uma operação cara.

MonkeyPushButton
fonte