SQL Server 2012 и 2016 Standard: если я добавлю логику if-else в хранимую процедуру для выполнения одной из двух ветвей кода, в зависимости от значения параметра, будет ли механизм кэшировать последнюю версию?
Нет, он кэширует все версии. Вернее, он кэширует одну версию со всеми исследованными путями , скомпилированными с переданными переменными.
Вот небольшая демонстрация с использованием базы данных Stack Overflow.
Создать индекс:
CREATE INDEX ix_yourmom ON dbo.Users (Reputation) INCLUDE (Id, DisplayName);
GO
Создайте хранимую процедуру с подсказкой индекса, которая указывает на несуществующий индекс в разветвленном коде.
CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS
BEGIN
IF @Reputation = 1
BEGIN
SELECT u.Id, u.DisplayName, u.Reputation
FROM dbo.Users AS u WITH (INDEX = PK_Users_Id)
WHERE u.Reputation = @Reputation;
END;
IF @Reputation > 1
BEGIN
SELECT u.Id, u.DisplayName, u.Reputation
FROM dbo.Users AS u WITH (INDEX = ix_yourdad)
WHERE u.Reputation = @Reputation;
END;
END;
Если я выполняю этот сохраненный процесс в поисках Reputation = 1, я получаю ошибку.
EXEC dbo.YourMom @Reputation = 1;
Сообщение 308, Уровень 16, Состояние 1, Процедура YourMom, Строка 14 [Пакетная строка запуска 32] Индекс 'ix_yourdad' для таблицы 'dbo.Users' (указанный в предложении FROM) не существует.
Если мы исправим имя индекса и повторно запустим запрос, кэшированный план будет выглядеть следующим образом:
Внутри XML будет две ссылки на @Reputation
переменную.
<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" />
Немного более простым тестом было бы просто получить примерный план для хранимого процесса. Вы можете увидеть, как оптимизатор исследует оба пути:
И если при следующем выполнении значение параметра изменится, будет ли он перекомпилирован и повторно кэширован хранимую процедуру, потому что должна выполняться другая ветвь кода? (Этот запрос довольно дорогой для компиляции.) Спасибо.
Нет, он сохранит значение времени выполнения первой компиляции.
Если мы повторно выполнить с другим @Reputation
:
EXEC dbo.YourMom @Reputation = 2;
Из фактического плана :
<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" ParameterRuntimeValue="(2)" />
У нас все еще есть скомпилированное значение 1, но теперь значение времени выполнения 2.
В кеше плана, который вы можете проверить с помощью бесплатного инструмента, подобного тому, который разрабатывает моя компания, sp_BlitzCache :
Хранимая процедура была вызвана дважды, и каждый оператор в ней был вызван один раз.
Так что у нас есть? Один кэшированный план для обоих запросов в хранимой процедуре.
Если вам нужна такая разветвленная логика, вам придется вызывать хранимые процедуры:
CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS
BEGIN
IF @Reputation = 1
BEGIN
EXEC dbo.Reputation1Query;
END;
IF @Reputation > 1
BEGIN
EXEC dbo.ReputationGreaterThan1Query;
END;
END;
Или динамический SQL:
DECLARE @sql NVARCHAR(MAX) = N''
SET @sql +=
N'
SELECT u.Id, u.DisplayName, u.Reputation
FROM dbo.Users AS u '
IF @Reputation = 1
BEGIN
SET @sql += N' (INDEX = PK_Users_Id)
WHERE u.Reputation = @Reputation;'
END;
IF @Reputation > 1
BEGIN
SET @sql += ' WITH (INDEX = ix_yourmom)
WHERE u.Reputation = @Reputation;'
END;
EXEC sys.sp_executesql @sql;
Надеюсь это поможет!