De vez em quando, surge uma conversa em que as pessoas estão convencidas de que os comentários têm ou não impacto no desempenho.
Em geral, direi que não, comentários não afetam o desempenho , mas sempre há espaço para um aviso "depende". Vamos criar um banco de dados de exemplo e uma tabela cheia de lixo:
CREATE DATABASE CommentTesting; GO USE CommentTesting; GO SELECT TOP (1000) n = NEWID(), * INTO dbo.SampleTable FROM sys.all_columns ORDER BY NEWID(); GO CREATE UNIQUE CLUSTERED INDEX x ON dbo.SampleTable(n); GO
Agora, quero criar quatro procedimentos armazenados – um com 20 caracteres de comentários, um com 2.000, um com 20.000 e outro com 200.000. E eu quero fazer isso novamente onde os comentários são incorporados *dentro* de uma instrução de consulta dentro do procedimento, em vez de serem independentes (o que afetará o XML do plano). Por fim, repeti o processo adicionando
OPTION (RECOMPILE)
à consulta. DECLARE @comments nvarchar(max) = N'', @basesql nvarchar(max), @sql nvarchar(max); SELECT TOP (5000) -- * 40 character strings @comments += N'--' + RTRIM(NEWID()) + CHAR(13) + CHAR(10) FROM sys.all_columns; SET @basesql = N'CREATE PROCEDURE dbo.$name$ AS BEGIN SET NOCOUNT ON; /* $comments1$ */ DECLARE @x int; SELECT @x = COUNT(*) /* $comments2$ */ FROM dbo.SampleTable OPTION (RECOMPILE); END'; SET @sql = REPLACE(REPLACE(@basesql, N'$name$', N'Small_Separate'), N'$comments1$', LEFT(@comments, 20)); EXEC sys.sp_executesql @sql; SET @sql = REPLACE(REPLACE(@basesql, N'$name$', N'Medium_Separate'), N'$comments1$', LEFT(@comments, 2000)); EXEC sys.sp_executesql @sql; SET @sql = REPLACE(REPLACE(@basesql, N'$name$', N'Large_Separate'), N'$comments1$', LEFT(@comments, 20000)); EXEC sys.sp_executesql @sql; SET @sql = REPLACE(REPLACE(@basesql, N'$name$', N'ExtraLarge_Separate'), N'$comments1$', LEFT(@comments, 200000)); EXEC sys.sp_executesql @sql; SET @sql = REPLACE(REPLACE(@basesql, N'$name$', N'Small_Embedded'), N'$comments2$', LEFT(@comments, 20)); EXEC sys.sp_executesql @sql; SET @sql = REPLACE(REPLACE(@basesql, N'$name$', N'Medium_Embedded'), N'$comments2$', LEFT(@comments, 2000)); EXEC sys.sp_executesql @sql; SET @sql = REPLACE(REPLACE(@basesql, N'$name$', N'Large_Embedded'), N'$comments2$', LEFT(@comments, 20000)); EXEC sys.sp_executesql @sql; SET @sql = REPLACE(REPLACE(@basesql, N'$name$', N'ExtraLarge_Embedded'), N'$comments2$', LEFT(@comments, 200000)); EXEC sys.sp_executesql @sql;
Agora, eu precisava gerar o código para executar cada procedimento 100.000 vezes, medir a duração de
sys.dm_exec_procedure_stats
, e também verifique o tamanho do plano em cache. DECLARE @hammer nvarchar(max) = N''; SELECT @hammer += N' DBCC FREEPROCCACHE; DBCC DROPCLEANBUFFERS; GO EXEC dbo.' + [name] + N'; GO 100000 SELECT [size of ' + [name] + ' (b)] = DATALENGTH(definition) FROM sys.sql_modules WHERE [object_id] = ' + CONVERT(varchar(32),([object_id])) + N'; SELECT [size of ' + [name] + ' (b)] = size_in_bytes FROM sys.dm_exec_cached_plans AS p CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t WHERE t.objectid = ' + CONVERT(varchar(32),([object_id])) + N'; SELECT N''' + [name] + N''', avg_dur = total_elapsed_time*1.0/execution_count FROM sys.dm_exec_procedure_stats WHERE [object_id] = ' + CONVERT(varchar(32),([object_id])) + N';' FROM sys.procedures WHERE [name] LIKE N'%[_]Separate' OR [name] LIKE N'%[_]Embedded'; PRINT @hammer;
Primeiro, vamos ver o tamanho dos corpos do procedimento. Sem surpresas aqui, apenas confirmando que meu código de construção acima gerou o tamanho esperado de comentários em cada procedimento:
Procedimento | Tamanho (bytes) |
---|---|
Pequeno_Separado / Pequeno_Incorporado | 378 |
Medium_Separate / Medium_Embedded | 4.340 |
Grande_Separado / Grande_Separado | 40.338 |
ExtraLarge_Separate / ExtraLarge_Separate | 400.348 |
Em seguida, qual o tamanho dos planos no cache?
Procedimento | Tamanho (bytes) |
---|---|
Pequeno_Separado / Pequeno_Incorporado | 40.360 |
Medium_Separate / Medium_Embedded | 40.360 |
Grande_Separado / Grande_Separado | 40.360 |
ExtraLarge_Separate / ExtraLarge_Separate | 40.360 |
Finalmente, como foi a apresentação? Sem
OPTION (RECOMPILE)
, aqui está o tempo médio de execução, em milissegundos – bastante consistente em todos os procedimentos:Duração média (milissegundos) – sem OPÇÃO (RECOMPILAR)
Com
OPTION (RECOMPILE)
no nível de instrução , podemos ver cerca de 50% de acerto na duração média em comparação com nenhuma recompilação, mas ainda assim bastante uniforme:Duração média (milissegundos) – com OPÇÃO (RECOMPILAR)
Em ambos os casos, enquanto a
OPTION (RECOMPILE)
a versão geralmente era mais lenta, havia praticamente ZERO diferença no tempo de execução, independentemente do tamanho do comentário no corpo do procedimento. E os custos de compilação mais altos?
Em seguida, eu queria ver se esses comentários grandes teriam um grande impacto nos custos de compilação, por exemplo, se os procedimentos fossem criados
WITH RECOMPILE
. O código de construção acima foi fácil de alterar para explicar isso. Mas neste caso, eu não poderia confiar em sys.dm_exec_procedure_stats
, porque isso não funciona para procedimentos WITH RECOMPILE
. Então meu código de geração para o teste foi um pouco diferente, pois eu teria que acompanhar a duração média manualmente:DECLARE @hammer nvarchar(max) = N''; SELECT @hammer += N' DBCC FREEPROCCACHE; DBCC DROPCLEANBUFFERS; SELECT SYSDATETIME(); GO EXEC dbo.' + [name] + N'; GO 100000 SELECT SYSDATETIME();'; PRINT @hammer;
Nesse caso, não consegui verificar o tamanho dos planos em cache, mas consegui determinar o tempo médio de execução dos procedimentos, e houve uma diferença com base no tamanho do comentário (ou, talvez, apenas no tamanho do corpo do procedimento):
Duração média (milissegundos) – COM RECOMPILAR no nível do procedimento em>
Se juntarmos todos em um gráfico, fica claro o quanto mais caro o
WITH RECOMPILE
uso pode ser:Duração média (milissegundos) – comparando os três métodos
Provavelmente vou dar uma olhada nisso mais tarde para ver exatamente onde esse taco de hóquei entra em jogo – imagino testes em incrementos de 10.000 caracteres. Por enquanto, porém, estou bastante satisfeito por ter respondido à pergunta.
Resumo
Os comentários parecem não estar relacionados ao desempenho real e observável do procedimento armazenado, exceto no caso em que o procedimento é definido
WITH RECOMPILE
. Pessoalmente, não vejo mais isso sendo usado na natureza, mas YMMV. Para as diferenças sutis entre esta opção e OPTION (RECOMPILE)
no nível de instrução , veja o artigo de Paul White, "Parameter Sniffing, Embedding, and the RECOMPILE Options". Pessoalmente, acho que os comentários podem ser extremamente valiosos para quem precisa revisar, manter ou solucionar problemas de seu código. Isso inclui você no futuro. Eu recomendo não se preocupar com o impacto no desempenho de uma quantidade razoável de comentários e, em vez disso, concentre-se em priorizar a utilidade do contexto que os comentários fornecem. Como alguém no Twitter disse, há um limite. Se seus comentários forem a versão resumida de Guerra e Paz, você pode considerar – correndo o risco de dissociar o código de sua documentação – colocar essa documentação em outro lugar e fazer referência ao link nos comentários do corpo do procedimento.
Para minimizar o risco de desacoplamento ou a documentação e o código ficarem fora de sincronia com o tempo, você pode criar um segundo procedimento, com o sufixo
_documentation
ou _comments
, e colocando os comentários (ou uma versão comentada do código) lá. Talvez colocá-lo em um esquema diferente para mantê-lo fora das listas de classificação principais. Pelo menos a documentação permanece com o banco de dados onde quer que vá, embora não garanta que será mantida. É lamentável que um procedimento normal não possa ser criado WITH SCHEMABINDING
, caso em que você pode vincular explicitamente o procedimento de comentário à fonte.