Индекс SEEK не используется разве что OPTION (RECOMPILE)?

11

(Вопрос перенесен с ТАК)

У меня таблица (фиктивные данные) с кластерным индексом содержит 2 столбца:

введите описание изображения здесь

Теперь я запускаю эти два запроса:

declare 
@productid int =1 , 
@priceid  int = 1




SELECT productid,
       t.priceID
FROM   Transactions AS t
WHERE  (productID = @productid OR @productid IS NULL)
       AND (priceid = @priceid OR @priceid IS NULL)  


SELECT productid,
       t.priceID
FROM   Transactions AS t
WHERE  (productID = @productid)
       AND (priceid = @priceid)

Фактический план выполнения для обоих запросов:

введите описание изображения здесь

Как видите, первый использует SCAN, а второй использует SEEK.

Однако - добавив OPTION (RECOMPILE)к первому запросу, составил план выполнения и для использования SEEK:

введите описание изображения здесь

Друзья в чате DBA сказали мне, что:

В вашем запросе @ productid = 1, что означает, что (productID = @ productID OR @productID IS NULL) можно упростить до (productID = @ productID). Первый требует сканирования для работы с любым значением @productID, второй может использовать поиск. Поэтому, когда вы используете RECOMPILE, SQL Server посмотрит, какое значение вы на самом деле имеете в @productID, и составит лучший план для него. При ненулевом значении в @productID поиск является лучшим. Если значение @productID неизвестно, план должен соответствовать любому возможному значению в @productID, что потребует сканирования. Имейте в виду: OPTION (RECOMPILE) будет принудительно перекомпилировать план при каждом его запуске, что будет добавлять несколько миллисекунд к каждому выполнению. Хотя это проблема, только если запрос выполняется очень часто.

Также :

Если @productID равен нулю, какое значение вы бы искали? Ответ: искать нечего. Все значения соответствуют требованиям.

Я понимаю, что OPTION (RECOMPILE)вынуждает SQL Server видеть, какие фактические значения имеют параметры, и посмотреть, может ли он с ним ПОИСКАТЬ.

Но теперь я теряю преимущество предварительной компиляции.

Вопрос

ИМХО - СКАНИРОВАНИЕ произойдет, только если параметр равен нулю.
Это нормально - пусть SQL SERVER создаст план выполнения для SCAN.
НО, если SQL Server видит, что я выполняю этот запрос много раз со значениями: 1,1почему тогда он не создает ДРУГОЙ план выполнения и не использует для этого SEEK?

AFAIK - SQL создает план выполнения для наиболее популярных запросов .

  • Почему SQL SERVER не сохраняет план выполнения для:

    @productid int =1 , @priceid int = 1

(Я запускаю его много раз с этими значениями)

  • Можно ли заставить SQL сохранить этот план выполнения (который использует SEEK) - для будущего вызова?

Полный сценарий создания таблицы + данные

Ройи Намир
источник

Ответы:

10

Подводя итог некоторым из основных моментов нашего обсуждения в чате :


Вообще говоря, SQL Server кэширует отдельный план для каждого оператора . Этот план должен быть действительным для всех возможных будущих значений параметров .

Невозможно кэшировать план поиска для вашего запроса, потому что этот план будет недействительным, если, например, @productid имеет значение null.

В некоторых будущих выпусках SQL Server может поддерживать один план, который динамически выбирает между сканированием и поиском, в зависимости от значений параметров времени выполнения, но это не то, что мы имеем сегодня.

Общий класс проблемы

Ваш запрос является примером шаблона, по-разному называемого запросом «поймать все» или «динамический поиск». Существуют различные решения, каждое из которых имеет свои преимущества и недостатки. В современных версиях SQL Server (2008+) основными параметрами являются:

  • IF блоки
  • OPTION (RECOMPILE)
  • Использование динамического SQL sp_executesql

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

IF блоки

Чтобы проиллюстрировать IFблочное решение для конкретного случая в вопросе:

IF @productid IS NOT NULL AND @priceid IS NOT NULL
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.productID = @productid
        AND T.priceID = @priceid;
END;
ELSE IF @productid IS NOT NULL
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.productID = @productid;
END;
ELSE IF @priceid IS NOT NULL
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.priceID = @priceid;
END;
ELSE
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T;
END;

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

Существует потенциальная проблема с анализом параметров, которая может потребовать OPTIMIZE FORподсказки для каждого запроса. Пожалуйста, ознакомьтесь с разделом ссылок, чтобы изучить эти типы тонкостей.

Рекомпилированные

Как отмечено выше в вопросе, вы также можете добавить OPTION (RECOMPILE)подсказку, чтобы получить новый план (поиск или сканирование) при каждом вызове. Учитывая относительно медленную частоту вызовов в вашем случае (в среднем каждые десять секунд, со временем компиляции менее миллисекунды), вероятно, этот вариант подойдет вам:

SELECT
    T.productID,
    T.priceID
FROM dbo.Transactions AS T
WHERE
    (T.productID = @productid OR @productid IS NULL)
    AND (T.priceID = @priceid OR @priceid IS NULL)
OPTION (RECOMPILE);

Кроме того, можно творчески комбинировать функции из вышеперечисленных вариантов, чтобы максимально использовать преимущества каждого из методов, одновременно сводя к минимуму недостатки. На самом деле нет никакого способа понять все это подробно, а затем сделать осознанный выбор, подкрепленный реалистичным тестированием.

дальнейшее чтение

Пол Уайт 9
источник