Af en toe duikt er een gesprek op waarin mensen ervan overtuigd zijn dat opmerkingen wel of geen invloed hebben op de prestaties.
In het algemeen zal ik zeggen dat, nee, opmerkingen geen invloed hebben op de prestaties , maar er is altijd ruimte voor een "het hangt ervan af"-disclaimer. Laten we een voorbeelddatabase en een tabel vol rommel maken:
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 wil ik vier opgeslagen procedures maken - één met 20 tekens aan opmerkingen, één met 2000, één met 20.000 en één met 200.000. En ik wil dat opnieuw doen waar de opmerkingen zijn ingesloten *in* een query-instructie binnen de procedure, in plaats van onafhankelijk te zijn (wat een effect zal hebben op de plan-XML). Ten slotte herhaalde ik het proces door OPTION (RECOMPILE)
toe te voegen op de vraag.
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 moest ik de code genereren om elke procedure 100.000 keer uit te voeren, de duur meten van sys.dm_exec_procedure_stats
, en controleer ook de grootte van het plan in de 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;
Laten we eerst eens kijken naar de grootte van de procedure-instanties. Geen verrassingen hier, ik bevestig alleen dat mijn constructiecode hierboven de verwachte grootte van opmerkingen in elke procedure genereerde:
Procedure | Grootte (bytes) |
---|---|
Small_Separate / Small_Embedded | 378 |
Medium_Separate / Medium_Embedded | 4.340 |
Large_Separate / Large_Separate | 40.338 |
ExtraLarge_Separate / ExtraLarge_Separate | 400.348 |
Vervolgens, hoe groot waren de plannen in de cache?
Procedure | Grootte (bytes) |
---|---|
Small_Separate / Small_Embedded | 40.360 |
Medium_Separate / Medium_Embedded | 40.360 |
Large_Separate / Large_Separate | 40.360 |
ExtraLarge_Separate / ExtraLarge_Separate | 40.360 |
Ten slotte, hoe was het optreden? Zonder OPTION (RECOMPILE)
, hier is de gemiddelde uitvoeringstijd, in milliseconden - redelijk consistent voor alle procedures:
Gemiddelde duur (milliseconden) – zonder OPTIE (HERCOMPIEREN)
Met OPTION (RECOMPILE)
. op instructieniveau , we kunnen over de hele linie een gemiddelde duur van ongeveer 50% zien vergeleken met geen hercompilatie, maar nog steeds redelijk gelijk:
Gemiddelde duur (milliseconden) – met OPTIE (HERCOMPIEREN)
In beide gevallen, terwijl de OPTION (RECOMPILE)
versie liep over het algemeen langzamer, er was vrijwel NUL verschil in runtime, ongeacht de grootte van de opmerkingen in de hoofdtekst van de procedure.
Hoe zit het met hogere compilatiekosten?
Vervolgens wilde ik zien of deze grote opmerkingen een enorme impact zouden hebben op de compileerkosten, bijvoorbeeld als de procedures WITH RECOMPILE
zouden zijn gemaakt . De constructiecode hierboven was eenvoudig te wijzigen om hiermee rekening te houden. Maar in dit geval kon ik niet vertrouwen op sys.dm_exec_procedure_stats
, omdat dit niet werkt voor procedures WITH RECOMPILE
. Dus mijn generatiecode voor de test was een beetje anders, omdat ik de gemiddelde duur handmatig zou moeten bijhouden:
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;
In dit geval kon ik de grootte van de plannen in de cache niet controleren, maar ik kon de gemiddelde looptijd van de procedures bepalen, en er was een verschil op basis van de grootte van de opmerkingen (of misschien alleen de lichaamsgrootte van de procedure):
Gemiddelde duur (milliseconden) – MET RECOMPILE op procedureniveau
Als we ze allemaal samen in een grafiek zetten, is het duidelijk hoeveel duurder de WITH RECOMPILE
gebruik kan zijn:
Gemiddelde duur (milliseconden) – alle drie de methoden vergelijken
Ik zal dit waarschijnlijk op een later tijdstip nader bekijken om te zien waar die hockeystick precies in het spel komt - ik zie testen in stappen van 10.000 tekens. Maar voor nu ben ik redelijk tevreden dat ik de vraag heb beantwoord.
Samenvatting
Opmerkingen lijken helemaal niets te maken te hebben met de werkelijke, waarneembare prestaties van de opgeslagen procedure, behalve in het geval dat de procedure is gedefinieerd WITH RECOMPILE
. Persoonlijk zie ik dit niet meer in het wild worden gebruikt, maar YMMV. Voor de subtiele verschillen tussen deze optie en OPTION (RECOMPILE)
, zie het artikel van Paul White, "Parameter Sniffing, Embedding, and the RECOMPILE Options."
Persoonlijk denk ik dat opmerkingen zeer waardevol kunnen zijn voor iedereen die uw code moet beoordelen, onderhouden of problemen moet oplossen. Dit omvat toekomstige jij. Ik raad ten zeerste af om je zorgen te maken over de prestatie-impact van een redelijk aantal opmerkingen, en in plaats daarvan te focussen op het prioriteren van het nut van de context die de opmerkingen bieden. Zoals iemand op Twitter al zei, er is een limiet. Als uw opmerkingen neerkomen op de verkorte versie van Oorlog en vrede, kunt u overwegen - met het risico de code los te koppelen van de documentatie - die documentatie ergens anders te plaatsen en naar de link in de opmerkingen van de proceduretekst te verwijzen.
Om het risico op ontkoppeling te minimaliseren, of dat de documentatie en code in de loop van de tijd niet meer synchroon lopen, kunt u een tweede procedure maken, met het achtervoegsel _documentation
of _comments
, en de opmerkingen (of een becommentarieerde versie van de code) daar te plaatsen. Zet het misschien in een ander schema om het uit de hoofdsorteerlijsten te houden. De documentatie blijft in ieder geval bij de database waar deze ook naartoe gaat, hoewel het niet garandeert dat het zal worden onderhouden. Het is jammer dat er geen normale procedure kan worden gemaakt WITH SCHEMABINDING
, in dat geval zou je de commentaarprocedure expliciet aan de bron kunnen koppelen.