Коли можна предикати SARGable висунути в CTE або в похідну таблицю?


15

Мішок з піском

Під час роботи на високому якість Блог Posts®, я натрапив на якому - то оптимізатор поведінки я знайшов дійсно сказ цікавого. У мене не одразу є пояснення, принаймні, не одне, з чим я задоволений, тому я вкладаю його на випадок, якщо хтось розумний з’явиться.

Якщо ви хочете слідувати далі, ви можете захопити тут версію 2013-го дамп-файлу даних про переповнення стека . Я використовую таблицю коментарів з одним додатковим індексом.

CREATE INDEX [ix_ennui] ON [dbo].[Comments] ( [UserId], [Score] DESC );

Запит перший

Коли я так запитую таблицю, я отримую незвичайний план запитів .

WITH x
    AS
     (
         SELECT   TOP 101
                  c.UserId, c.Text, c.Score
         FROM     dbo.Comments AS c
         ORDER BY c.Score DESC
     )
SELECT *
FROM   x
WHERE  x.Score >= 500;

Горіхи

Присудок SARGable на бал не засувається всередину CTE. Це в операторі фільтру набагато пізніше в плані.

Горіхи

Що мені здається дивним, оскільки це ORDER BYзнаходиться в тому ж стовпці, що і фільтр.

Запит два

Якщо я поміняю запит, він все-таки висувається.

WITH x
    AS
     (
         SELECT   c.UserId, c.Text, c.Score
         FROM     dbo.Comments AS c
     )
SELECT TOP 101 *
FROM   x
WHERE  x.Score >= 500
ORDER BY x.Score DESC;

План запитів теж змінюється і працює набагато швидше, без розливу на диск. Вони обидва дають однакові результати, при цьому присудок при некластеризованому скануванні індексів.

Горіхи

Горіхи

Запит три

Це еквівалент написання запиту так:

SELECT   TOP 101
         c.UserId, c.Text, c.Score
FROM     dbo.Comments AS c
WHERE c.Score >= 500
ORDER BY c.Score DESC;

Запит четвертий

Використання похідної таблиці отримує той же «поганий» план запитів, що і початковий CTE-запит

SELECT *
FROM   (   SELECT   TOP 101
                    c.UserId, c.Text, c.Score
           FROM     dbo.Comments AS c
           ORDER BY c.Score DESC ) AS x
WHERE x.Score >= 500;

Все стає ще дивнішим, коли ...

Я змінюю запит, щоб замовити дані у зростанні, а фільтр - <=.

Щоб не ставити це питання надто довго, я збираюся все скласти разом.

Запити

--Derived table
SELECT *
FROM   (   SELECT   TOP 101
                    c.UserId, c.Text, c.Score
           FROM     dbo.Comments AS c
           ORDER BY c.Score ASC ) AS x
WHERE x.Score <= 500;


--TOP inside CTE
WITH x
    AS
     (
         SELECT   TOP 101
                  c.UserId, c.Text, c.Score
         FROM     dbo.Comments AS c
         ORDER BY c.Score ASC
     )
SELECT *
FROM   x
WHERE  x.Score <= 500;


--Written normally
SELECT   TOP 101
         c.UserId, c.Text, c.Score
FROM     dbo.Comments AS c
WHERE c.Score <= 500
ORDER BY c.Score ASC;

--TOP outside CTE
WITH x
    AS
     (
         SELECT   c.UserId, c.Text, c.Score
         FROM     dbo.Comments AS c
     )
SELECT TOP 101 *
FROM   x
WHERE  x.Score <= 500
ORDER BY x.Score ASC;

Плани

Посилання на план .

Горіхи

Зауважте, що жоден із цих запитів не користується перевагою некластеризованого індексу - єдине, що тут змінюється, - це позиція оператора фільтра. Ні в якому разі предикат не підштовхується до доступу до індексу.

З'являється питання!

Чи є причина, що предикат SARGable може бути висунутий в одних сценаріях, а не в інших? Відмінності в запитах, відсортованих у порядку зменшення, цікаві, але відмінності між тими і тими, що є висхідними, химерними.

Для всіх, хто цікавиться, ось плани з лише індексом на Score:

Відповіді:


11

Тут є кілька проблем.

Натискання наказує минуле TOP

Наразі оптимізатор не може просунути присудок минулого a TOP, навіть у обмежених випадках, коли це було б безпечно зробити *. Це обмеження пояснює поведінку всіх запитів у питанні, де предикат перебуває у більшій мірі, ніж TOP.

Робота навколо полягає у виконанні переписання вручну. Принципова проблема схожа на випадок натискання предикатів на минулу функцію вікна , за винятком того, що немає відповідного спеціалізованого правила на кшталт SelOnSeqPrj.

Моя особиста думка полягає в тому, що подібне правило розвідки SelOnTopзалишається нездійсненним, оскільки люди навмисно писали запити TOP, намагаючись надати своєрідну огорожу для оптимізації.

* Як правило, це означає, що присудок повинен міститись у ORDER BYпункті, пов'язаному з TOP, а напрямок будь-якої нерівності має відповідати напрямку сортування. Трансформація також повинна враховувати сортування поведінки NULL в SQL Server. Загалом, обмеження, ймовірно, означають, що ця трансформація в цілому не буде достатньо корисною на практиці, щоб виправдати додаткові зусилля з розвідки.

Питання витрат

Решта планів виконання у запитанні можна пояснити як вибір на основі витрат через розподіл значень у Scoreстовпці (набагато більше рядків <= 500, ніж> = 500), і ефект від цілі рядка, введеної TOP.

Наприклад, запит:

--Written normally
SELECT TOP (101)
    c.UserId, 
    c.[Text],
    c.Score
FROM dbo.Comments AS c
WHERE
    c.Score <= 500
ORDER BY
    c.Score ASC;

... створює план із, мабуть, незапущеним предикатом у фільтрі:

пізній фільтр через ціль рядка

Зауважте, що за сортуванням оцінюється кількість 101 рядка. Це ефект від мети рядка, яку додає Топ. Це впливає на орієнтовну вартість Сортування та Фільтр достатньо, щоб здатися, що це найдешевший варіант. Орієнтовна вартість цього плану становить 2401,39 одиниць.

Якщо вимкнути цілі рядків із підказкою:

--Written normally
SELECT TOP (101)
    c.UserId, 
    c.[Text],
    c.Score
FROM dbo.Comments AS c
WHERE
    c.Score <= 500
ORDER BY
    c.Score ASC
OPTION (USE HINT ('DISABLE_OPTIMIZER_ROWGOAL'));

... План виконання:

план без мети рядка

Присудок було висунуто в сканування як залишковий нестандартний предикат, а вартість всього плану - 2402,32 одиниці.

Зауважте, що <= 500присудок не передбачає фільтрації жодних рядків. Якби ви вибрали меншу кількість, наприклад <= 50, оптимізатор віддав би перевагу плану висунутого предикату незалежно від ефекту цілі рядка.

Для запиту з Score DESCі Score >= 500присудка:

--Written normally
SELECT TOP (101)
    c.UserId, 
    c.[Text],
    c.Score
FROM dbo.Comments AS c
WHERE
    c.Score >= 500
ORDER BY
    c.Score DESC;

Тепер очікується, що предикат буде дуже вибірковим, тому оптимізатор вибирає натиснути присудок і використовувати некластеризований індекс з пошуками:

вибірковий присудок

Знову ж таки, оптимізатор розглядав декілька альтернатив і вибрав це як, мабуть, найдешевший варіант, як завжди.

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