Database
 sql >> Base de Dados >  >> RDS >> Database

Os comentários podem prejudicar o desempenho do procedimento armazenado?


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.