Індекс SEEK не використовується, якщо OPTION (RECOMPILE)?


11

(Питання переміщено з SO)

У мене є таблиця (фіктивних даних) з кластерним індексом, що містить 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 АБО @productID НУЛЬНИЙ) можна спростити до (productID = @ productID). Перший вимагає сканування для роботи з будь-яким значенням @productID, другий може використовувати пошук. Отже, використовуючи RECOMPILE, SQL Server вивчить, яке значення ви насправді маєте у @productID, і складе найкращий план для цього. З ненульовим значенням у @productID найкраще шукати. Якщо значення @productID невідоме, план повинен відповідати будь-якому можливому значенню в @productID, яке вимагало б сканування. Будьте попереджені: OPTION (RECOMPILE) примушує перекомпілювати план кожен раз, коли ви його запускаєте, що додасть кілька мілісекунд до кожного виконання. Хоча це лише проблема, якщо запит працює дуже часто.

Також:

Якщо @productID недійсний, до якого значення ви б шукали? Відповідь: ні до чого прагнути. Усі значення відповідають вимогам.

Я розумію, що OPTION (RECOMPILE)змушує SQL Server бачити, які фактичні значення має параметри, і бачити, чи може він ШЕКНУТИ з ним.

Але зараз я втрачаю вигоду від попередньої компіляції.

Питання

IMHO - SCAN відбудеться лише в тому випадку, якщо параметр є нульовим.
Це добре - нехай SQL SERVER створить план виконання для SCAN.
АЛЕ, якщо SQL Server бачить, що я виконую цей запит багато разів зі значеннями: 1,1тоді чому він не створює ДРУГИЙ план виконання та не використовує SEEK для цього

AFAIK - SQL створює план виконання для найбільш звернених запитів .

  • Чому SQL SERVER не зберігає план виконання для:

    @productid int =1 , @priceid int = 1

(Я запускаю його багато разів із цими значеннями)

  • Чи можна змусити SQL утримувати той план виконання (який використовує SEEK) - для майбутнього виклику?

Повне створення скриптів таблиці + даних


Відповіді:


10

Підсумовуючи деякі основні моменти нашого обговорення в чаті :


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

Неможливо кешувати план пошуку для вашого запиту, оскільки цей план не був би дійсним, якщо, наприклад, @productid є недійсним.

У деякому майбутньому випуску 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);

Також можна творчими способами поєднувати функції з перерахованих вище варіантів, щоб максимально використати переваги кожного методу, мінімізуючи при цьому мінуси. Дійсно не існує ярлика для детального розуміння цього матеріалу, а потім зробити усвідомлений вибір, підкріплений реалістичним тестуванням.

Подальше читання

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.