sql >> Databasteknik >  >> RDS >> Database

Kan kommentarer hämma prestandan för lagrad procedur?

Då och då dyker det upp en konversation där människor är övertygade om att kommentarer antingen har eller inte påverkar prestanda.

I allmänhet kommer jag att säga att nej, kommentarer påverkar inte resultatet , men det finns alltid utrymme för en "det beror på" ansvarsfriskrivning. Låt oss skapa en exempeldatabas och en tabell full med skräp:

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

Nu vill jag skapa fyra lagrade procedurer – en med 20 tecken i kommentarer, en med 2000, en med 20 000 och en med 200 000. Och jag vill göra det igen där kommentarerna är inbäddade *i* en frågesats i proceduren, i motsats till att vara oberoende (vilket kommer att ha en effekt på planens XML). Slutligen upprepade jag processen och lade till OPTION (RECOMPILE) till frågan.

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;

Nu behövde jag generera koden för att köra varje procedur 100 000 gånger, mäta varaktigheten från sys.dm_exec_procedure_stats , och kontrollera även storleken på planen i cachen.

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;

Låt oss först titta på storleken på förfarandeorganen. Inga överraskningar här, jag bekräftar bara att min byggkod ovan genererade den förväntade storleken på kommentarer i varje procedur:

Procedur Storlek (byte)
Small_Separate / Small_Embedded 378
Medium_Separate / Medium_Embedded 4 340
Large_Separate / Large_Separate 40 338
ExtraLarge_Separate / ExtraLarge_Separate 400 348


Nästa, hur stora var planerna i cachen?

Procedur Storlek (byte)
Small_Separate / Small_Embedded 40 360
Medium_Separate / Medium_Embedded 40 360
Large_Separate / Large_Separate 40 360
ExtraLarge_Separate / ExtraLarge_Separate 40 360


Till sist, hur var föreställningen? Utan OPTION (RECOMPILE) , här är den genomsnittliga körtiden, i millisekunder – ganska konsekvent över alla procedurer:


Genomsnittlig varaktighet (millisekunder) – utan ALTERNATIV (OMKOMPILERA)

Med satsnivå OPTION (RECOMPILE) , vi kan se ungefär 50 % träff i genomsnittlig varaktighet över hela linjen jämfört med ingen omkompilering, men ändå ganska jämn:


Genomsnittlig varaktighet (millisekunder) – med ALTERNATIV (OMKOMPILERA)

I båda fallen, medan OPTION (RECOMPILE) versionen körde i allmänhet långsammare, det var praktiskt taget NOLL skillnad i körtid, oavsett kommentarstorlek i procedurtexten.

Vad sägs om högre sammanställningskostnader?

Därefter ville jag se om dessa stora kommentarer skulle ha en enorm inverkan på kompileringskostnaderna, till exempel om procedurerna skapades WITH RECOMPILE . Byggkoden ovan var lätt att ändra för att ta hänsyn till detta. Men i det här fallet kunde jag inte lita på sys.dm_exec_procedure_stats , eftersom detta inte fungerar för procedurer WITH RECOMPILE . Så min generationskod för testet var lite annorlunda, eftersom jag skulle behöva spåra genomsnittlig varaktighet manuellt:

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;

I det här fallet kunde jag inte kontrollera storleken på planerna i cachen, men jag kunde bestämma den genomsnittliga körtiden för procedurerna, och det fanns en skillnad baserat på kommentarsstorlek (eller kanske bara procedurens storlek):


Genomsnittlig varaktighet (millisekunder) – MED ÅTERKOMPILERING på procedurnivå

Om vi ​​sätter ihop dem alla i en graf är det tydligt hur mycket dyrare WITH RECOMPILE är användning kan vara:


Genomsnittlig varaktighet (millisekunder) – jämför alla tre metoderna

Jag kommer förmodligen att titta närmare på detta vid ett senare tillfälle för att se exakt var den där hockeyklubban spelar in – jag tänker mig att testa i steg om 10 000 tecken. För nu är jag dock ganska nöjd med att jag har svarat på frågan.

Sammanfattning

Kommentarer verkar vara helt orelaterade till faktisk, observerbar lagrad procedurprestanda, förutom i de fall då proceduren är definierad WITH RECOMPILE . Personligen ser jag inte att detta används i det vilda längre, men YMMV. För de subtila skillnaderna mellan detta alternativ och OPTION (RECOMPILE) på satsnivå , se Paul Whites artikel, "Parameter Sniffing, Embedding, and the RECOMPILE Options."

Personligen tror jag att kommentarer kan vara extremt värdefulla för alla som måste granska, underhålla eller felsöka din kod. Detta inkluderar framtida dig. Jag rekommenderar starkt att inte oroa dig för prestandan av en rimlig mängd kommentarer, och istället fokusera på att prioritera användbarheten av sammanhanget som kommentarerna ger. Som någon på Twitter sa, det finns en gräns. Om dina kommentarer uppgår till den förkortade versionen av War and Peace, kan du överväga – med risk för att frikoppla koden från dess dokumentation – att lägga den dokumentationen någon annanstans och hänvisa till länken i procedurorganets kommentarer.

För att minimera risken för frikoppling, eller att dokumentationen och koden på annat sätt blir osynkroniserade med tiden, kan du skapa en andra procedur, med suffixet _documentation eller _comments , och placera kommentarerna (eller en kommenterad version av koden) där. Lägg det kanske i ett annat schema för att hålla det borta från huvudsorteringslistorna. Åtminstone finns dokumentationen kvar i databasen vart den än går, även om den inte garanterar att den kommer att underhållas. Det är olyckligt att en normal procedur inte kan skapas WITH SCHEMABINDING , i så fall kan du uttryckligen koppla kommentarsproceduren till källan.


  1. Hur undkommer jag ett enstaka citat i SQL Server?

  2. hur lägger man till superprivilegier till mysql-databasen?

  3. Bästa tillvägagångssätt för grupperade löpande summor

  4. MySQL COUNT() – Få antalet rader som ska returneras av en fråga