От време на време се появява разговор, в който хората са убедени, че коментарите оказват или не оказват влияние върху производителността.
Като цяло ще кажа, че не, коментарите не влияят на производителността , но винаги има място за отказ от отговорност „зависи“. Нека създадем примерна база данни и таблица, пълна с боклуци:
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
Сега искам да създам четири съхранени процедури – една с 20 знака коментари, една с 2000, една с 20 000 и една с 200 000. И искам да направя това отново, когато коментарите са вградени *в* израз на заявка в рамките на процедурата, за разлика от това да бъдат независими (което ще има ефект върху XML на плана). Накрая повторих процеса, като добавих OPTION (RECOMPILE)
към заявката.
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;
Сега трябваше да генерирам кода, за да стартирам всяка процедура 100 000 пъти, да измеря продължителността от sys.dm_exec_procedure_stats
, а също така проверете размера на плана в кеша.
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;
Първо, нека разгледаме размера на процедурите. Тук няма изненади, просто потвърждавам, че моят строителен код по-горе генерира очаквания размер на коментарите във всяка процедура:
Процедура | Размер (байтове) |
---|---|
Small_Separate / Small_Embedded | 378 |
Средно_отделно/средно_вградено | 4340 |
Големи_разделени / Големи_разделени | 40 338 |
ExtraLarge_Separate / ExtraLarge_Separate | 400 348 |
След това колко големи бяха плановете в кеша?
Процедура | Размер (байтове) |
---|---|
Small_Separate / Small_Embedded | 40 360 |
Средно_отделно/средно_вградено | 40 360 |
Големи_разделени / Големи_разделени | 40 360 |
ExtraLarge_Separate / ExtraLarge_Separate | 40 360 |
Накрая какво беше представянето? Без OPTION (RECOMPILE)
, ето средното време за изпълнение в милисекунди – доста последователно във всички процедури:
Средна продължителност (милисекунди) – без ОПЦИЯ (РЕКОМПИЛИРАНЕ)
С OPTION (RECOMPILE)
на ниво оператор , можем да видим около 50% попадение в средната продължителност в сравнение с липсата на прекомпилация, но все пак доста равномерно:
Средна продължителност (милисекунди) – с ОПЦИЯ (РЕКОМПИЛИРАНЕ)
И в двата случая, докато OPTION (RECOMPILE)
версията като цяло работеше по-бавно, на практика имаше НУЛА разлика във времето на изпълнение, независимо от размера на коментара в тялото на процедурата.
Ами по-високите разходи за компилация?
След това исках да видя дали тези големи коментари ще имат огромно влияние върху разходите за компилиране, например ако процедурите са създадени WITH RECOMPILE
. Строителният код по-горе беше лесен за промяна, за да се отчете това. Но в този случай не можех да разчитам на sys.dm_exec_procedure_stats
, защото това не работи за процедури WITH RECOMPILE
. Така че кодът ми за генериране на теста беше малко по-различен, тъй като трябваше да проследявам средната продължителност ръчно:
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;
В този случай не можах да проверя размера на плановете в кеша, но успях да определя средното време на изпълнение на процедурите и имаше разлика въз основа на размера на коментара (или, може би, просто размера на тялото на процедурата):
Средна продължителност (милисекунди) – С ПРЕКОМПИЛИРАНЕ на ниво процедура
Ако ги сложим заедно на графика, става ясно колко по-скъпо е WITH RECOMPILE
употреба може да бъде:
Средна продължителност (милисекунди) – сравняване на трите метода
Вероятно ще разгледам това по-отблизо по-късно, за да видя къде точно тази хокейна пръчка влиза в игра – предвиждам тестване на стъпки от 10 000 знака. Засега обаче съм доста доволен, че отговорих на въпроса.
Резюме
Коментарите изглеждат напълно несвързани с действителната, наблюдавана производителност на съхранената процедура, освен в случая, когато процедурата е дефинирана WITH RECOMPILE
. Лично аз не виждам това да се използва повече в дивата природа, но YMMV. За фините разлики между тази опция и на ниво израз OPTION (RECOMPILE)
, вижте статията на Пол Уайт, "Подушване на параметри, вграждане и опциите за ПРЕКОМПИЛИРАНЕ."
Лично аз смятам, че коментарите могат да бъдат изключително ценни за всеки, който трябва да преглежда, поддържа или отстранява неизправности в кода ви. Това включва бъдещите ви. Силно препоръчвам да не се притеснявате за въздействието върху производителността на разумно количество коментари и вместо това да се съсредоточите върху приоритизирането на полезността на контекста, който предоставят коментарите. Както каза някой в Twitter, има ограничение. Ако вашите коментари се равняват на съкратената версия на War and Peace, може да помислите – с риск да отделите кода от неговата документация – да поставите тази документация на друго място и да препратите към връзката в коментарите на тялото на процедурата.
За да сведете до минимум риска от отделяне или документацията и кодът иначе да не се синхронизират с течение на времето, можете да създадете втора процедура с наставка _documentation
или _comments
и поставяне на коментарите (или коментирана версия на кода) там. Може би го постави в друга схема, за да го държи извън основните списъци за сортиране. Поне документацията остава в базата данни, където и да отиде, въпреки че не гарантира, че ще бъде поддържана. Жалко е, че нормална процедура не може да бъде създадена WITH SCHEMABINDING
, в който случай бихте могли изрично да обвържете процедурата за коментар с източника.