SQL Server - если логика в хранимой процедуре и плане кеша

15

SQL Server 2012 и 2016 Standard:

Если я добавлю if-elseлогику в хранимую процедуру для выполнения одной из двух ветвей кода, в зависимости от значения параметра, кеширует ли движок последнюю версию?

И если при следующем выполнении значение параметра изменится, будет ли он перекомпилирован и повторно кэширован хранимую процедуру , поскольку должна выполняться другая ветвь кода? (Этот запрос довольно дорог для компиляции.)

Николь Г.
источник

Ответы:

27

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;

Надеюсь это поможет!

Эрик Дарлинг
источник