Почему характеристики этих двух запросов настолько разные?

У меня есть сохраненный proc, который ищет продукты (250 000 строк), используя полный текстовый индекс.

Сохраненный proc принимает параметр, который является условием полного текстового поиска. Этот параметр может быть нулевым, поэтому я добавил нулевую проверку, и запрос неожиданно начал работать на порядки медленнее.

-- This is normally a parameter of my stored proc
DECLARE @Filter VARCHAR(100)
SET @Filter = 'FORMSOF(INFLECTIONAL, robe)'

-- #1 - Runs < 1 sec
SELECT TOP 100 ID FROM dbo.Products
WHERE CONTAINS(Name, @Filter)

-- #2 - Runs in 18 secs
SELECT TOP 100 ID FROM dbo.Products
WHERE @Filter IS NULL OR CONTAINS(Name, @Filter)

Вот планы выполнения:

Запрос № 1 Execution plant #1

Запрос № 2 Execution plant #2

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

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

Ответ 1

OR может сокрушить производительность, поэтому сделайте так:

DECLARE @Filter VARCHAR(100)
SET @Filter = 'FORMSOF(INFLECTIONAL, robe)'

IF @Filter IS NOT NULL
BEGIN
    SELECT TOP 100 ID FROM dbo.Products
    WHERE CONTAINS(Name, @Filter)
END
ELSE
BEGIN
    SELECT TOP 100 ID FROM dbo.Products
END

Посмотрите на эту статью: Динамические условия поиска в T-SQL от Erland Sommarskog и этот вопрос: SQL Server 2008 - Условный запрос.

Ответ 2

Первый план запроса выглядит просто:

  • полнотекстовый поиск для решения CONTAINS(Name, @Filter)
  • сканирование индекса для поиска других столбцов согласованных строк
  • объединить два, используя хеш-соединение

Оператор конкатенации образует объединение двух наборов записей. Таким образом, похоже на второй запрос:

  • сканирование индекса (позже использовалось для поиска других столбцов)
  • постоянное сканирование. Я предполагаю, что он обрабатывает ваш запрос как не параметризованный, поэтому план запроса не должен работать ни для какого другого значения @Filter. Если правильно, постоянное сканирование разрешает @Filter is not null.
  • полнотекстовый поиск для решения CONTAINS(Name, @Filter)
  • объединяет результат 3 с пустым множеством из 2 Цикл
  • объединяет результаты 1 и 4 для поиска других столбцов.

Хеш объединяет торговую память для скорости; если ваша система имеет достаточно памяти, она намного быстрее, чем объединение цикла. Это может легко объяснить 10-100-кратное замедление.

Одно исправление заключается в использовании двух разных запросов:

if @Filter is null
    SELECT TOP 100 ID FROM dbo.Products
else
    SELECT TOP 100 ID FROM dbo.Products WHERE CONTAINS(Name, @Filter)

Ответ 3

Вы ввели условие ИЛИ. В большинстве случаев намного проще проверить явно для NULL и выполнить один запрос против вашего метода.

Например, попробуйте следующее:

IF @Filter IS NULL
 BEGIN
SELECT TOP 100 ID FROM dbo.Products
END
ELSE
BEGIN
SELECT TOP 100 ID FROM dbo.Products
WHERE @Filter CONTAINS(Name, @Filter)
END