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.