Assumindo que "custo" é em termos de tempo (embora não tenha certeza do que mais poderia ser em termos de ;-), pelo menos você deve ter uma noção disso fazendo algo como o seguinte:
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases'; -- replace with your proc
SET STATISTICS TIME OFF;
O primeiro item relatado na guia "Mensagens" deve ser:
Tempo de análise e compilação do SQL Server:
Eu executaria isso pelo menos 10 vezes e calcularia a média dos milissegundos "CPU" e "Elapsed".
Idealmente, você executaria isso no Production para obter uma estimativa de tempo real, mas raramente as pessoas têm permissão para limpar o cache do plano no Production. Felizmente, a partir do SQL Server 2008, foi possível limpar um plano específico do cache. Nesse caso, você pode fazer o seguinte:
DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
SELECT DISTINCT stat.plan_handle
FROM sys.dm_exec_query_stats stat
CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
+ CONVERT(NVARCHAR(130), cte.plan_handle, 1)
+ N');'
+ NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases' -- replace with your proc
SET STATISTICS TIME OFF;
No entanto, dependendo da variabilidade dos valores transmitidos para o (s) parâmetro (s) que causam o plano em cache "ruim", há outro método a considerar que é o meio termo entre OPTION(RECOMPILE)
e OPTION(OPTIMIZE FOR UNKNOWN)
: Dynamic SQL. Sim, eu disse. E eu até quero dizer SQL dinâmico não parametrizado. Aqui está o porquê.
Você tem claramente dados que têm uma distribuição desigual, pelo menos em termos de um ou mais valores de parâmetros 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 que os valores dos parâmetros passados novamente sejam idênticos às execuções anteriores. Para procs que são chamados com freqüência - uma vez a cada poucos segundos ou mais frequentemente - isso o salvará de uma situação horrível ocasional, mas ainda o deixará em uma situação sempre não tão boa.
OPTION(OPTIMIZE FOR (@Param = value))
irá gerar um plano com base nesse valor específico, o que pode ajudar vários casos, mas ainda deixa você aberto ao problema atual.
OPTION(OPTIMIZE FOR UNKNOWN)
gerará um plano com base no que equivale a uma distribuição média, o que ajudará algumas consultas, mas prejudicará outras. 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 passados tenham seus próprios planos de consulta separados, ideais (bem, tanto quanto 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 consomem memória. Os custos menores são:
Necessidade de Validar Parâmetros de Cadeia de Caracteres para Evitar Injeções de SQL
possivelmente precisando configurar um certificado e um usuário baseado em certificado para manter a abstração de segurança ideal, pois o SQL dinâmico requer permissões diretas de tabela.
Então, aqui está como eu gerenciei essa situação quando tive procs chamados mais de uma vez por segundo e atingindo várias tabelas, cada uma com milhões de linhas. Eu tentei, OPTION(RECOMPILE)
mas isso provou ser muito prejudicial para o processo nos 99% dos casos que não tinham o parâmetro sniffing / bad cache plan problem. E lembre-se de que um desses procs tinha cerca de 15 consultas e apenas 3 - 5 delas foram convertidas em Dynamic SQL, 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 com distribuições de dados altamente díspares (e, portanto, causam esse problema) e quais são usados com colunas com distribuições mais uniformes (e não devem ser causando esse problema).
Crie a cadeia de caracteres Dynamic SQL usando parâmetros para os parâmetros de entrada proc associados a colunas distribuídas uniformemente. Essa parametrização ajuda a reduzir o aumento resultante nos planos de execução no cache relacionado a esta consulta.
Para os parâmetros restantes associados a distribuições altamente variadas, esses devem ser concatenados no SQL dinâmico como valores literais. Como uma consulta exclusiva é determinada por quaisquer alterações no texto da consulta, ter WHERE StatusID = 1
é uma consulta diferente e, portanto, um plano de consulta diferente do que ter WHERE StatusID = 2
.
Se algum dos parâmetros de entrada proc que devem ser concatenados no texto da consulta forem cadeias, eles deverão ser validados para se proteger contra a injeção SQL (embora isso seja menos provável de acontecer se as cadeias transmitidas forem geradas pelo aplicativo e não um usuário, mas ainda). Faça pelo menos isso REPLACE(@Param, '''', '''''')
para garantir que as aspas simples se tornem aspas simples.
Se necessário, crie um certificado que será usado para criar um usuário e assine o procedimento armazenado, de modo que as permissões diretas da tabela sejam concedidas apenas ao novo usuário baseado em certificado e não para [public]
ou para usuários que não deveriam ter essas permissões. .
Exemplo proc:
CREATE PROCEDURE MySchema.MyProc
(
@Param1 INT,
@Param2 DATETIME,
@Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'
SELECT tab.Field1, tab.Field2, ...
FROM MySchema.SomeTable tab
WHERE tab.Field3 = @P1
AND tab.Field8 >= CONVERT(DATETIME, ''' +
CONVERT(NVARCHAR(50), @Param2, 121) +
N''')
AND tab.Field2 LIKE N''' +
REPLACE(@Param3, N'''', N'''''') +
N'%'';';
EXEC sp_executesql
@SQL,
N'@P1 INT',
@P1 = @Param1;