Оптимизация планов выполнения для параметризованных запросов T-SQL, содержащих функции окна

EDIT: я обновил код примера и предоставил полную таблицу и просмотрел реализации для справки, но основной вопрос остается без изменений.

У меня довольно сложное представление в базе данных, к которой я пытаюсь выполнить запрос. Когда я пытаюсь получить набор строк из представления путем жесткого кодирования предложения WHERE к определенным значениям внешнего ключа, представление выполняется очень быстро с оптимальным планом выполнения (индексы используются правильно и т.д.).

SELECT * 
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = 20

Однако, когда я пытаюсь добавить параметры к запросу, внезапно мой план выполнения разваливается. Когда я запускаю запрос ниже, я получаю сканирование индексов вместо поиска по всему месту, а производительность запросов очень плохая.

DECLARE @ForeignKeyCol int = 20

SELECT * 
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = @ForeignKeyCol 

Я использую SQL Server 2008 R2. Что здесь дает? Что такое использование параметров, которые вызывают субоптимальный план? Любая помощь будет принята с благодарностью.

Для справки, вот определения объектов, для которых я получаю ошибку.

CREATE TABLE [dbo].[BaseTable]
(
    [PrimaryKeyCol] [uniqueidentifier] PRIMARY KEY,
    [ForeignKeyCol] [int] NULL,
    [DataCol] [binary](1000) NOT NULL
)

CREATE NONCLUSTERED INDEX [IX_BaseTable_ForeignKeyCol] ON [dbo].[BaseTable]
(
    [ForeignKeyCol] ASC
)

CREATE VIEW [dbo].[ViewOnBaseTable]
AS
SELECT
    PrimaryKeyCol,
    ForeignKeyCol,
    DENSE_RANK() OVER (PARTITION BY ForeignKeyCol ORDER BY PrimaryKeyCol) AS ForeignKeyRank,
    DataCol
FROM
    dbo.BaseTable

Я уверен, что функция окна - это проблема, но я фильтрую свой запрос на одно значение, которое оконная функция разделяет, поэтому я бы ожидал, что оптимизатор сначала будет фильтровать, а затем запустит функцию окна. Он делает это в жестко закодированном примере, но не в параметризованном примере. Ниже приведены два плана запросов. Верхний план хорош, а нижний план плох.

Query Execution Plans

Ответ 1

При использовании OPTION (RECOMPILE) обязательно посмотрите план пост-исполнения ( "фактический" ), а не предварительный ( "оцененный" ). Некоторые оптимизации применяются только при выполнении:

DECLARE @ForeignKeyCol int = 20;

SELECT ForeignKeyCol, ForeignKeyRank
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = @ForeignKeyCol
OPTION (RECOMPILE);

План предварительного исполнения:

Pre-execution plan

План после исполнения:

Post-execution plan

Протестировано на SQL Server 2012 build 11.0.3339 и SQL Server 2008 R2 build 10.50.4270

Фон и ограничения

Когда в SQL Server 2005 были добавлены функции оконной перестройки, оптимизатор не имел возможности выталкивать выборку из этих новых прогнозов последовательности. Чтобы устранить некоторые распространенные сценарии, в которых это вызвало проблемы с производительностью, SQL Server 2008 добавил новое правило упрощения, SelOnSeqPrj, которое позволяет выбирать подходящие варианты выбора, где значение является константой. Эта константа может быть литералом в тексте запроса или оцененным значением параметра, полученного с помощью OPTION (RECOMPILE). Нет особой проблемы с NULLs, хотя для этого запроса может потребоваться ANSI_NULLS OFF. Насколько я знаю, применение упрощения к постоянным значениям является ограничением реализации; нет особых причин, по которым он не может быть расширен для работы с переменными. Я помню, что правило SelOnSeqPrj рассматривало наиболее часто встречающиеся проблемы с производительностью.

Параметрирование

Правило SelOnSeqPrj не применяется, когда запрос успешно автопараметризирован. Не существует надежного способа определить, была ли автоматическая параметризация запроса в SSMS, это только указывает на попытку автопарама. Чтобы быть ясным, присутствие держателей мест, таких как [@0], показывает только, что была предпринята попытка автоматической параметризации. Надежный способ определить, был ли подготовлен план для повторного использования, заключается в проверке кеша плана, где "параметризованный дескриптор плана" обеспечивает связь между специальными и подготовленными планами.

Например, в SSMS автоматически настраивается следующий запрос:

SELECT *
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = 20;

Но кеш плана показывает иначе:

WITH XMLNAMESPACES
(
    DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan'
)
SELECT
    parameterized_plan_handle =
        deqp.query_plan.value('(//StmtSimple)[1]/@ParameterizedPlanHandle', 'nvarchar(64)'), 
    parameterized_text =
        deqp.query_plan.value('(//StmtSimple)[1]/@ParameterizedText', 'nvarchar(max)'),
    decp.cacheobjtype,
    decp.objtype,
    decp.plan_handle
FROM sys.dm_exec_cached_plans AS decp
CROSS APPLY sys.dm_exec_sql_text(decp.plan_handle) AS dest
CROSS APPLY sys.dm_exec_query_plan(decp.plan_handle) AS deqp
WHERE
    dest.[text] LIKE N'%ViewOnBaseTable%'
    AND dest.[text] NOT LIKE N'%dm_exec_cached_plans%';

Adhoc plan cache entry

Если параметр базы данных принудительной параметризации включен, мы получаем параметризованный результат, в котором оптимизация не применяется:

ALTER DATABASE Sandpit SET PARAMETERIZATION FORCED;
DBCC FREEPROCCACHE;

SELECT *
FROM dbo.ViewOnBaseTable
WHERE ForeignKeyCol = 20;

Forced parameterization plan

В запросе кеша плана теперь отображается параметризованный кешированный план, связанный параметризованным дескриптором плана:

Parameterized plan cache

Обход

По возможности, мое предпочтение заключается в том, чтобы переписать представление как встроенную функцию таблицы, где предполагаемая позиция выбора может быть сделана более явной (если необходимо):

CREATE FUNCTION dbo.ParameterizedViewOnBaseTable
    (@ForeignKeyCol integer)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
    SELECT
        bt.PrimaryKeyCol,
        bt.ForeignKeyCol,
        ForeignKeyRank = DENSE_RANK() OVER (
            PARTITION BY bt.ForeignKeyCol 
            ORDER BY bt.PrimaryKeyCol),
        bt.DataCol
    FROM dbo.BaseTable AS bt
    WHERE
        bt.ForeignKeyCol = @ForeignKeyCol;

Запрос будет выглядеть следующим образом:

DECLARE @ForeignKeyCol integer = 20;
SELECT pvobt.*
FROM dbo.ParameterizedViewOnBaseTable(@ForeignKeyCol) AS pvobt;

С планом выполнения:

Function plan

Ответ 2

Вы всегда можете использовать способ CROSS APPLY.

ALTER VIEW [dbo].[ViewOnBaseTable]
AS
SELECT
    PrimaryKeyCol,
    ForeignKeyCol,
    ForeignKeyRank,
    DataCol
FROM (
    SELECT DISTINCT
        ForeignKeyCol
    FROM dbo.BaseTable
) AS Src
CROSS APPLY (
    SELECT
        PrimaryKeyCol,
        DENSE_RANK() OVER (ORDER BY PrimaryKeyCol) AS ForeignKeyRank,
        DataCol
    FROM dbo.BaseTable AS B
    WHERE B.ForeignKeyCol = Src.ForeignKeyCol
) AS X

Ответ 3

Я думаю, что в этом конкретном случае это может быть из-за того, что типы данных между вашими параметрами и вашей таблицей не соответствуют точно таким образом, что SQL Server должен выполнять неявное преобразование, которое не является режимом sargable.

Проверьте типы данных таблиц и сделайте свои параметры одинаковыми. Или сделайте бросок вне запроса.