Предполагая, что «стоимость» выражается в терминах времени (хотя и не уверен, что еще это может быть в смысле ;-), тогда, по крайней мере, вы сможете почувствовать это, выполнив что-то вроде следующего:
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases'; -- replace with your proc
SET STATISTICS TIME OFF;
Первый элемент, указанный на вкладке «Сообщения», должен быть следующим:
SQL Server анализирует и компилирует время:
Я бы запустил это как минимум 10 раз и усреднил бы и «ЦП», и «Истекшие» миллисекунды.
В идеале вы должны запустить это в Production, чтобы получить точную оценку времени, но людям редко разрешают очищать кэш плана в Production. К счастью, начиная с SQL Server 2008 стало возможным очистить конкретный план из кэша. В этом случае вы можете сделать следующее:
DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
SELECT DISTINCT stat.plan_handle
FROM sys.dm_exec_query_stats stat
CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
+ CONVERT(NVARCHAR(130), cte.plan_handle, 1)
+ N');'
+ NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases' -- replace with your proc
SET STATISTICS TIME OFF;
Однако, в зависимости от изменчивости значений, передаваемых для параметра (ов), вызывающих «плохой» кэшированный план, существует другой метод, который следует рассматривать как промежуточное звено между OPTION(RECOMPILE)
и OPTION(OPTIMIZE FOR UNKNOWN)
: Динамический SQL. Да, я сказал это. И я даже имею в виду непараметрический динамический SQL. Вот почему.
У вас явно есть данные с неравномерным распределением, по крайней мере, с точки зрения одного или нескольких значений входных параметров. Недостатки упомянутых вариантов:
OPTION(RECOMPILE)
будет генерировать план для каждого исполнения , и вы никогда не будете в состоянии извлечь выгоду из любого плана повторного использования, даже если значение параметров , передаваемое снова идентично предыдущее выполнение (ов). Для процедур, которые вызываются часто - раз в несколько секунд или чаще - это спасет вас от случайной ужасной ситуации, но все же оставит вас в ситуации, которая не всегда так велика.
OPTION(OPTIMIZE FOR (@Param = value))
создаст план на основе этого конкретного значения, которое может помочь в нескольких случаях, но все же оставит вас открытым для текущей проблемы.
OPTION(OPTIMIZE FOR UNKNOWN)
создаст план, основанный на том, что составляет среднее распределение, что поможет некоторым запросам, но повредит другим. Это должно быть то же самое, что и опция использования локальных переменных.
Однако динамический SQL, если все сделано правильно , позволит различным передаваемым значениям иметь свои собственные отдельные планы запросов, которые являются идеальными (ну, сколько бы они ни были). Основная стоимость здесь заключается в том, что по мере увеличения разнообразия значений, передаваемых в кэш-памяти, увеличивается количество планов выполнения в кэше, и они занимают память. Незначительные расходы:
необходимость проверки строковых параметров для предотвращения SQL-инъекций
возможно, потребуется настроить сертификат и пользователя на основе сертификатов для поддержания идеальной абстракции безопасности, поскольку для динамического SQL требуются прямые разрешения таблиц.
Итак, вот как я справился с этой ситуацией, когда у меня были прокы, которые вызывались более одного раза в секунду и работали с несколькими таблицами, каждая с миллионами строк. Я пытался, OPTION(RECOMPILE)
но это оказалось слишком вредным для процесса в 99% случаев, в которых не было проблемы с анализом параметров / плохим кэшированием плана. И, пожалуйста, имейте в виду, что в одном из этих процедур было около 15 запросов, и только 3-5 из них были преобразованы в динамический SQL, как описано здесь; Динамический SQL не использовался, если это не было необходимо для конкретного запроса.
Если в хранимой процедуре есть несколько входных параметров, выясните, какие из них используются со столбцами, которые имеют сильно разнородные распределения данных (и, следовательно, вызывают эту проблему), а какие используются со столбцами, которые имеют более равномерное распределение (и не должны вызывая эту проблему).
Создайте строку динамического SQL, используя параметры для входных параметров proc, которые связаны с равномерно распределенными столбцами. Эта параметризация помогает уменьшить результирующее увеличение планов выполнения в кеше, связанном с этим запросом.
Для остальных параметров, связанных с сильно варьируемыми дистрибутивами, их следует объединить в Dynamic SQL в виде литеральных значений. Поскольку уникальный запрос определяется любыми изменениями в тексте запроса, наличие WHERE StatusID = 1
запроса отличается от запроса и, следовательно, отличается от плана запроса WHERE StatusID = 2
.
Если какие-либо входные параметры proc, которые должны быть объединены в текст запроса, являются строками, их необходимо проверить для защиты от SQL-инъекций (хотя это менее вероятно, если передаваемые строки генерируются приложение а не пользователь, но все же). По крайней мере, сделайте так, REPLACE(@Param, '''', '''''')
чтобы одинарные кавычки стали экранированными.
При необходимости создайте сертификат, который будет использоваться для создания пользователя, и подпишите хранимую процедуру таким образом, чтобы прямые разрешения для таблиц были предоставлены только новому пользователю на основе сертификатов, а не пользователям [public]
или пользователям, которые в противном случае не имели бы таких разрешений. ,
Пример процедуры:
CREATE PROCEDURE MySchema.MyProc
(
@Param1 INT,
@Param2 DATETIME,
@Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'
SELECT tab.Field1, tab.Field2, ...
FROM MySchema.SomeTable tab
WHERE tab.Field3 = @P1
AND tab.Field8 >= CONVERT(DATETIME, ''' +
CONVERT(NVARCHAR(50), @Param2, 121) +
N''')
AND tab.Field2 LIKE N''' +
REPLACE(@Param3, N'''', N'''''') +
N'%'';';
EXEC sp_executesql
@SQL,
N'@P1 INT',
@P1 = @Param1;