Влияние на производительность условий sql 'OR', когда одна альтернатива тривиальна?

Я создаю хранимую процедуру для поиска некоторых данных в моей базе данных в соответствии с некоторыми критериями, введенными пользователем.

Мой код sql выглядит так:

Create Procedure mySearchProc
(
@IDCriteria bigint=null,
...
@MaxDateCriteria datetime=null
)
as
select Col1,...,Coln from MyTable 
where (@IDCriteria is null or [email protected])
...
and (@MaxDateCriteria is null or Date<@MaxDateCriteria)

Изменить : у меня около 20 возможных параметров, и может произойти каждая комбинация n ненулевых параметров.

Можно ли писать такой код с точки зрения производительности? (Я использую MS SQL Server 2008)

Будет ли генерация кода SQL, содержащего только необходимые предложения where, значительно быстрее?

Ответов (12)

Решение
where (@IDCriteria is null or [email protected])
  and (@MaxDateCriteria is null or Date<@MaxDateCriteria)

Если вы напишете этот критерий, то SQL-сервер не будет знать, что лучше использовать: индекс для идентификаторов или индекс для дат.

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

IF @IDCriteria is not null and @MaxDateCriteria is not null

  --query
  WHERE ID = @IDCriteria and Date < @MaxDateCriteria

ELSE IF @IDCriteria is not null

  --query
  WHERE ID = @IDCriteria

ELSE IF @MaxDateCriteria is not null

  --query
  WHERE Date < @MaxDateCriteria

ELSE

  --query
  WHERE 1 = 1

Если вы ожидаете, что вам потребуются разные планы из оптимизатора, вам нужно написать разные запросы, чтобы получить их !!

Будет ли создание кода SQL, содержащего только необходимые предложения where, значительно быстрее?

Да - если вы ожидаете, что оптимизатор будет выбирать между разными планами.


Редактировать:

DECLARE @CustomerNumber int, @CustomerName varchar(30)

SET @CustomerNumber = 123
SET @CustomerName = '123'

SELECT * FROM Customers
WHERE (CustomerNumber = @CustomerNumber OR @CustomerNumber is null)
  AND (CustomerName = @CustomerName OR @CustomerName is null)

CustomerName и CustomerNumber проиндексированы. Оптимизатор говорит: «Сканирование кластерного индекса с распараллеливанием». Вы не можете написать худший запрос для одной таблицы.


Изменить: у меня около 20 возможных параметров, и может произойти каждая комбинация n ненулевых параметров.

У нас была аналогичная функция «поиска» в нашей базе данных. Когда мы посмотрели на фактические отправленные запросы, 99,9% из них использовали AccountIdentifier. В вашем случае я подозреваю, что либо всегда предоставляется один столбец, либо всегда предоставляется один из двух столбцов. Это привело бы к 2 или 3 случаям соответственно.

Не важно удалять операторы OR из всей структуры. Важно удалить операторы OR из столбца / столбцов, которые, как вы ожидаете, оптимизатор будет использовать для доступа к индексам.

это один из случаев, когда я использую построение кода или sproc для каждого поиска. поскольку ваш поиск настолько сложен, я бы пошел на создание кода. вы можете сделать это либо в коде, либо с помощью динамического sql. просто будьте осторожны с SQL-инъекцией.

Операторы OR печально известны тем, что вызывают проблемы с производительностью в основном потому, что требуют сканирования таблиц. Если вы можете написать запрос без OR, вам будет лучше.

Относительно "Будет ли создание кода SQL, содержащего только необходимые предложения where, быть заметно быстрее?"

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

Вы можете выполнять выборочные запросы в порядке наиболее распространенных / эффективных (индексированных и т. Д.) Параметров и добавлять PK во временную таблицу.

Это создаст (надеюсь, небольшое!) Подмножество данных

Затем присоедините эту временную таблицу к основной таблице, используя полное предложение WHERE с

SELECT ...
FROM @TempTable AS T
    JOIN dbo.MyTable AS M
        ON M.ID = T.ID
WHERE (@IDCriteria IS NULL OR [email protected])
...
AND (@MaxDateCriteria IS NULL OR M.Date<@MaxDateCriteria)

стиль для уточнения (небольшого) подмножества.

Что, если бы такие конструкции были заменены:

WHERE (@IDCriteria IS NULL OR @IDCriteria=ID)
AND (@MaxDateCriteria IS NULL OR Date<@MaxDateCriteria)
AND ...

с такими:

WHERE ID = ISNULL(@IDCriteria, ID)
AND Date < ISNULL(@MaxDateCriteria, DATEADD(millisecond, 1, Date))
AND ...

или это просто покрытие того же неоптимизируемого запроса синтаксическим сахаром?

Оптимизатору сложно выбрать правильный индекс. IMO, это один из немногих случаев, когда динамический SQL - лучший вариант.

Итак, чтобы свести к минимуму приведенные выше комментарии:

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

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

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

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

Пример: поиск заказов по клиенту; или найти заказы по диапазону дат; или найти заказы от продавца.

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

Вы все равно можете попасть в категорию «Все остальные». Но, по крайней мере, тогда, если вы предоставите то, что по сути является открытой формой запроса по примеру, тогда пользователи будут иметь некоторое представление о том, во что они ввязываются. Выполнение того, что вы описываете, на самом деле ставит вас в положение, когда вы пытаетесь перехитрить оптимизатор запросов, что является безумием, ИМХО.

В настоящее время я работаю с SQL 2005, поэтому я не знаю, работает ли оптимизатор 2008 по-другому. При этом я обнаружил, что вам нужно сделать пару вещей ...

  1. Убедитесь, что вы используете WITH (RECOMPILE) для своего запроса

  2. Используйте операторы CASE, чтобы вызвать короткое замыкание логики. По крайней мере, в 2005 году это НЕ делается с помощью операторов ИЛИ. Например:

.

SELECT
     ...
FROM
     ...
WHERE
     (1 =
          CASE
               WHEN @my_column IS NULL THEN 1
               WHEN my_column = @my_column THEN 1
               ELSE 0
          END
     )

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

Мое предложение - создать строку sql. Вы получите максимальную производительность от индексации и повторного использования плана выполнения.

DECLARE @sql nvarchar(4000);
SET @sql = N''

IF @param1 IS NOT NULL
    SET @sql = CASE WHEN @sql = N'' THEN N'' ELSE N' AND ' END + N'param1 = @param1';
IF @param2 IS NOT NULL
    SET @sql = CASE WHEN @sql = N'' THEN N'' ELSE N' AND ' END + N'param2 = @param2';
...
IF @paramN IS NOT NULL
    SET @sql = CASE WHEN @sql = N'' THEN N'' ELSE N' AND ' END + N'paramN = @paramN';

IF @sql <> N''
    SET @sql = N' WHERE ' + @sql;
SET @sql = N'SELECT ... FROM myTable' + @sql;

EXEC sp_executesql @sql, N'@param1 type, @param2 type, ..., @paramN type', @param1, @param2, ..., @paramN;

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

Я бы посоветовал:

  1. Создайте определенные SP для часто выполняемых путей выполнения (т. Е. Переданных наборов параметров), оптимизированных для каждого сценария.
  2. Оставьте основной общий SP для крайних случаев (предполагая, что они редко выполняются), но используйте предложение WITH RECOMPILE, чтобы создавать новый план выполнения при каждом запуске процедуры.

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