Запрос выполняется дольше, добавляя неиспользуемые условия WHERE - PullRequest
1 голос
/ 10 ноября 2009

Я наткнулся на интересную загадку (по крайней мере, мне интересно). Ниже приводится общее представление о том, как выглядит мой запрос. Предположим, что @AuthorType является входом для хранимой процедуры, и в каждом месте, где я размещаю комментарии, есть различные специализированные условия.

SELECT *
FROM TBooks
WHERE
(--...SOME CONDITIONS)
OR
(@AuthorType = 1 AND --...DIFFERENT CONDITIONS)
OR
(@AuthorType = 2 AND --...STILL MORE CONDITIONS)

Что меня интересует, так это то, что если я выполню этот SP с @AuthorType = 0, он будет работать медленнее, чем если бы я удалил два последних набора условий (те, которые добавляют условия для специализированных значений @AuthorType).

Разве SQL Server не должен осознавать во время выполнения, что эти условия никогда не будут выполнены, и полностью их игнорировать? Разница, которую я испытываю, не маленькая; это примерно удваивает длину запроса (1-2 секунды до 3-5 секунд).

Ожидаю ли я, что SQL Server слишком оптимизирует это для меня? Мне действительно нужно иметь 3 отдельных SP для особых условий?

Ответы [ 3 ]

6 голосов
/ 10 ноября 2009

Не должен SQL Server реализовать в время выполнения, что эти условия будут никогда не встретишь и полностью их игнорируешь?

Нет, абсолютно нет. Здесь действуют два фактора.

  1. SQL Server не гарантирует логическое короткое замыкание оператора. См. На коротком замыкании логического оператора SQL Server для примера, ясно показывающего, как оптимизация запросов может изменить порядок вычисления булевых выражений. Хотя на первый взгляд это кажется ошибкой императивного C, такого как мышление в программировании, это правильная вещь для декларативного множества ориентированного мира SQL.

  2. ИЛИ - враг SQL SARGability. Операторы SQL компилируются в план выполнения, затем план выполняется. План повторно используется между вызовами (кэшируется). Таким образом, компилятор SQL должен сгенерировать один единственный план, который подходит для всех отдельных случаев ИЛИ (@ AuthorType = 1 AND @ AuthorType = 2 AND @ AuthorType = 3). Когда дело доходит до генерации плана запроса, это точно так, как если бы @AuthorType имел бы все значения одновременно, в некотором смысле. Результатом почти всегда является наихудший возможный план, который не может принести никакой пользы ни одному индексу, потому что различные ветви ИЛИ противоречат друг другу, поэтому в конечном итоге он сканирует всю таблицу и проверяет строки по одной.

Лучшее, что можно сделать в вашем случае, а также в любом другом случае, в котором используется логическое ИЛИ, это переместить @AuthorType за пределы запроса:

IF (@AuthorType = 1)
  SELECT ... FROM ... WHERE ...
ELSE IF (@AuthorType = 2)
  SELECT ... FROM ... WHERE ...
ELSE ...

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

Следующая лучшая вещь - это использовать UNION ALL, способ, который уже предложил chadhoc, и это правильный подход в представлениях или других местах, где требуется одно утверждение (IF не допускается).

4 голосов
/ 10 ноября 2009

Это связано с тем, насколько сложно оптимизатору обрабатывать логику типа «ИЛИ» вместе с проблемами , связанными с параметром сниффинг . Попробуйте изменить свой запрос выше на подход UNION, как указано в посте здесь . то есть вы получите несколько операторов, объединенных вместе с одним @AuthorType = x AND, что позволит оптимизатору исключать части, где логика AND не соответствует заданному @AuthorType, и по очереди искать соответствующие индексы. будет выглядеть примерно так:

SELECT *
FROM TBooks
WHERE
(--...SOME CONDITIONS)
AND @AuthorType = 1 AND --...DIFFERENT CONDITIONS)
union all
SELECT *
FROM TBooks
WHERE
(--...SOME CONDITIONS)
AND @AuthorType = 2 AND --...DIFFERENT CONDITIONS)
union all
...
0 голосов
/ 10 ноября 2009

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

Будет ли это "чувствовать" лучше?

SELECT ... lots of columns and complicated stuff ...
FROM 
(
    SELECT MyPK
    FROM TBooks
    WHERE 
    (--...SOME CONDITIONS) 
    AND @AuthorType = 1 AND --...DIFFERENT CONDITIONS) 
    union all 
    SELECT MyPK
    FROM TBooks
    WHERE 
    (--...SOME CONDITIONS) 
    AND @AuthorType = 2 AND --...DIFFERENT CONDITIONS) 
    union all 
    ... 
) AS B1
JOIN TBooks AS B2
    ON B2.MyPK = B1.MyPK
JOIN ... other tables ...

Псевдотаблица B1 - это просто предложение WHERE для получения PK. Затем он присоединяется к исходной таблице (и любым другим, которые необходимы), чтобы получить «представление». Это позволяет избежать дублирования столбцов презентации в каждом UNION ALL

Вы можете сделать еще один шаг и сначала вставить PK во временную таблицу, а затем соединить ее с другими таблицами для аспекта представления.

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

DECLARE @MyTempTable TABLE
(
    MyPK int NOT NULL,
    PRIMARY KEY
    (
        MyPK
    )
)

IF @LastName IS NOT NULL
BEGIN
   INSERT INTO @MyTempTable
   (
        MyPK
   )
   SELECT MyPK
   FROM MyNamesTable
   WHERE LastName = @LastName -- Lets say we have an efficient index for this
END
ELSE
IF @Country IS NOT NULL
BEGIN
   INSERT INTO @MyTempTable
   (
        MyPK
   )
   SELECT MyPK
   FROM MyNamesTable
   WHERE Country = @Country -- Got an index on this one too
END

... etc

SELECT ... presentation columns
FROM @MyTempTable AS T
    JOIN MyNamesTable AS N
        ON N.MyPK = T.MyPK -- a PK join, V. efficient
    JOIN ... other tables ...
        ON ....
WHERE     (@LastName IS NULL OR Lastname @LastName)
      AND (@Country IS NULL OR Country @Country)

Обратите внимание, что все тесты повторяются [технически вам не нужен @Lastname один :)], включая непонятные, которые (допустим) не были в исходных фильтрах для создания @ MyTempTable.

Создание @MyTempTable разработано так, чтобы максимально использовать любые доступные параметры. Возможно, если доступны как @LastName, так и @Country, которые гораздо эффективнее заполняют таблицу, чем любой из них, поэтому мы создадим вариант для этого сценария.

Проблемы с масштабированием? Просмотрите фактические запросы и добавьте случаи для тех, которые можно улучшить.

...