Eu tenho um caso típico em que a detecção de parâmetros faz com que um plano de execução "ruim" chegue ao cache do plano, fazendo com que as execuções subsequentes do meu procedimento armazenado sejam muito lentas. Posso "resolver" esse problema com variáveis locais, OPTIMIZE FOR ... UNKNOWN
, e OPTION(RECOMPILE)
. No entanto, também posso mergulhar na consulta e tentar otimizá-la.
Estou tentando determinar se devo : devido ao tempo limitado para corrigir problemas, gostaria de saber o custo de não fazê-lo. A meu ver, se eu continuar com OPTION(RECOMPILE)
, o efeito líquido é que um plano de consulta é recriado toda vez que a consulta é executada. Então, acho que preciso saber:
Como saber qual é o custo de criar um plano de consultas?
Para responder à minha própria pergunta, pesquisei no Google (por exemplo, com esta consulta ) e examinei a documentação das colunas para o dm_exec_query_stats
DMV . Também inspecionei a janela de saída no SSMS para "Plano de consulta real" para encontrar essas informações. Por fim, pesquisei DBA.SE . Nenhum deles levou a uma resposta.
Alguém pode me dizer? É possível encontrar ou medir o tempo necessário para a criação do plano?
Você pode ver as propriedades do nó raiz no plano de consulta, por exemplo:
(captura de tela do Sentry One Plan Explorer gratuito )
Essas informações também estão disponíveis consultando o cache do plano, por exemplo, usando uma consulta baseada nos seguintes relacionamentos:
Para um tratamento completo das opções que você tem para lidar com esses tipos de consultas, consulte o artigo atualizado recentemente de Erland Sommarskog .
Supondo que "custo" seja em termos de tempo (embora não tenha certeza do que mais poderia ser em termos de ;-), então, pelo menos, você deve ser capaz de ter uma noção disso fazendo algo como o seguinte:
O primeiro item informado na aba "Mensagens" deve ser:
Eu executaria isso pelo menos 10 vezes e calcularia a média dos milissegundos de "CPU" e "Decorrido".
Idealmente, você executaria isso em Produção para obter uma estimativa de tempo real, mas raramente as pessoas têm permissão para limpar o cache do plano em Produção. Felizmente, a partir do SQL Server 2008, tornou-se possível limpar um plano específico do cache. Nesse caso, você pode fazer o seguinte:
No entanto, dependendo da variabilidade dos valores sendo passados para o(s) parâmetro(s) que causa(m) o plano em cache "ruim", há outro método a considerar que é um meio-termo entre
OPTION(RECOMPILE)
eOPTION(OPTIMIZE FOR UNKNOWN)
: SQL dinâmico. Sim, eu disse isso. E eu ainda quero dizer SQL dinâmico não parametrizado. Aqui está o porquê.Você claramente tem dados que têm uma distribuição desigual, pelo menos em termos de um ou mais valores de parâmetro de entrada. As desvantagens das opções mencionadas são:
OPTION(RECOMPILE)
irá gerar um plano para cada execução e você nunca poderá se beneficiar de qualquer reutilização do plano, mesmo se os valores dos parâmetros passados novamente forem idênticos à(s) execução(ões) anterior(es). Para procs que são chamados com frequência - uma vez a cada poucos segundos ou com mais frequência - isso o salvará de situações horríveis ocasionais, mas ainda o deixará em uma situação nem sempre tão boa assim.OPTION(OPTIMIZE FOR (@Param = value))
gerará um plano baseado naquele valor específico, o que pode ajudar em vários casos, mas ainda deixa você aberto ao problema atual.OPTION(OPTIMIZE FOR UNKNOWN)
gerará um plano baseado no que equivale a uma distribuição média, o que ajudará algumas consultas, mas prejudicará outras. Isso deve ser o mesmo que a opção de usar variáveis locais.O SQL dinâmico, no entanto, quando feito corretamente , permitirá que os vários valores transmitidos tenham seus próprios planos de consulta separados que são ideais (bem, tanto quanto eles serão). O principal custo aqui é que, à medida que a variedade de valores transmitidos aumenta, o número de planos de execução no cache aumenta e eles ocupam memória. Os custos menores são:
necessidade de validar parâmetros de string para evitar SQL Injections
possivelmente precisando configurar um certificado e um usuário baseado em certificado para manter a abstração de segurança ideal, já que o SQL dinâmico requer permissões diretas de tabela.
Então, aqui está como gerenciei essa situação quando tive procs que foram chamados mais de uma vez por segundo e atingiram várias tabelas, cada uma com milhões de linhas. Eu tentei,
OPTION(RECOMPILE)
mas isso provou ser muito prejudicial para o processo em 99% dos casos que não tinham o problema de detecção de parâmetro / plano de cache ruim. E lembre-se de que um desses procs tinha cerca de 15 consultas e apenas 3 a 5 deles foram convertidos em SQL dinâmico, conforme descrito aqui; O SQL dinâmico não foi usado, a menos que fosse necessário para uma consulta específica.Se houver vários parâmetros de entrada para o procedimento armazenado, descubra quais são usados com colunas que têm distribuições de dados altamente díspares (e, portanto, causam esse problema) e quais são usados com colunas que têm distribuições mais uniformes (e não devem ser causando este problema).
Construa a string SQL dinâmica usando parâmetros para os parâmetros de entrada proc que estão associados a colunas distribuídas uniformemente. Essa parametrização ajuda a reduzir o consequente aumento de planos de execução no cache relacionados a essa consulta.
Para os parâmetros restantes associados a distribuições muito variadas, eles devem ser concatenados no SQL dinâmico como valores literais. Como uma consulta exclusiva é determinada por qualquer alteração no texto da consulta, ter
WHERE StatusID = 1
é uma consulta diferente e, portanto, um plano de consulta diferente de terWHERE StatusID = 2
.Se algum dos parâmetros de entrada do proc que devem ser concatenados no texto da consulta forem strings, eles precisam ser validados para proteção contra SQL Injection (embora isso seja menos provável de acontecer se as strings passadas forem geradas pelo app e não um usuário, mas ainda assim). Pelo menos faça o
REPLACE(@Param, '''', '''''')
para garantir que as aspas simples se tornem aspas simples de escape.Se necessário, crie um certificado que será usado para criar um usuário e assine o procedimento armazenado de forma que as permissões diretas da tabela sejam concedidas apenas ao novo usuário baseado em certificado e não a
[public]
ou a usuários que não deveriam ter tais permissões .Processo de exemplo: