Когда предикаты SARGable могут быть помещены в CTE или производную таблицу?

15

принуждать

Работая над Top Quality Blog 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;

NUTS

Предикат SARGable для Score не помещается в CTE. Это в операторе фильтра намного позже в плане.

NUTS

Что я нахожу странным, так как 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;

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

NUTS

NUTS

Запрос третий

Это эквивалентно написанию запроса примерно так:

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

Query Four

При использовании производной таблицы получается тот же «плохой» план запроса, что и в исходном запросе 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;

планы

План ссылка .

NUTS

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

Возникает вопрос!

Есть ли причина, по которой предикат SARGable можно использовать в некоторых сценариях, а не в других? Различия в запросах, отсортированных в порядке убывания, интересны, но различия между теми и восходящими являются странными.

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

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

Ответы:

11

Здесь есть несколько проблем.

Отталкивание предикатов прошлого TOP

В настоящее время оптимизатор не может протолкнуть предикат за 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;

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

выборочный предикат

Опять же, оптимизатор рассмотрел несколько вариантов и, как обычно, выбрал этот вариант как наиболее дешевый.

Пол Уайт восстановил Монику
источник