оптимизация SQL Server-запросов со многими столбцами - PullRequest
2 голосов
/ 17 июня 2011

у нас есть таблица "Profile" с более чем 60 столбцами (Id, fname, lname, пол, состояние профиля, город, штат, степень, ...).

пользователи ищут других людей на сайте. запрос как:

WITH TempResult as (        
select ROW_NUMBER() OVER(ORDER BY @sortColumn DESC) as RowNum, profile.id from Profile 
where 
   (@a is null or a = @a) and
   (@b is null or b = @b) and
   ...(over 60 column)
)
SELECT profile.* FROM TempResult join profile on TempResult.id = profile.id
WHERE 
     (RowNum >= @FirstRow)
     AND 
     (RowNum <= @LastRow)

Сервер SQL по умолчанию использует кластеризованный индекс для выполнения запроса. но общее время выполнения превышает 300. мы тестируем другое решение, такое как многостолбцовый индекс во всех столбцах where, но общее время выполнения превышает 400. У вас есть какое-либо решение, чтобы общее время выполнения было меньше 100? мы используем sql server 2008.

Ответы [ 4 ]

3 голосов
/ 17 июня 2011

К сожалению, я не думаю, что для вашей проблемы есть чисто SQL-решение.Вот пара альтернатив:

  • Динамический SQL - создайте запрос, который включает только операторы предложения WHERE для значений, которые фактически предоставлены.Предполагая, что средний поиск фактически заполняет только 2-3 поля, индексы можно добавлять и использовать.
  • Полнотекстовый поиск - перейдите к чему-то более похожему на поиск по ключевым словам Google.Нет индивидуальных опций.
  • Lucene (или что-то еще) - Поиск вне SQL;Это довольно существенное изменение.

Еще один вариант, который я только что вспомнил, когда внедрял в систему.Создайте вертикальную таблицу, которая включает в себя все данные, по которым вы ищете, и создайте для них запрос.Это проще всего сделать с динамическим SQL, но это можно сделать с помощью Table Value Parameters или временной таблицы в крайнем случае.

Идея состоит в том, чтобы создать таблицу, которая выглядит примерно так:

  • Идентификатор профиля
  • Имя атрибута
  • Значение атрибута

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

В этой таблице у вас будут строки данных, такие как:

  • (1, 'city', 'grand rapids')
  • (1, «штат», «MI»)
  • (2, «город», «Детройт»)
  • (2, «штат», «MI»')

Тогда ваш SQL будет выглядеть примерно так:

SELECT *
FROM Profile
    JOIN (
        SELECT ProfileID
        FROM ProfileAttributes
        WHERE (AttributeName = 'city' AND AttributeValue = 'grand rapids')
            AND (AttributeName = 'state' AND AttributeValue = 'MI')
        GROUP BY ProfileID
        HAVING COUNT(*) = 2
    ) SelectedProfiles ON Profile.ProfileID = SelectedProfiles.ProfileID
... -- Add your paging here

Как я уже сказал, вы можете использовать временную таблицу с именем / значениями атрибута:

SELECT *
FROM Profile
    JOIN (
        SELECT ProfileID
        FROM ProfileAttributes
            JOIN PassedInAttributeTable ON ProfileAttributes.AttributeName = PassedInAttributeTable.AttributeName
                 AND ProfileAttributes.AttributeValue = PassedInAttributeTable.AttributeValue
        GROUP BY ProfileID
        HAVING COUNT(*) = CountOfRowsInPassedInAttributeTable -- calculate or pass in
    ) SelectedProfiles ON Profile.ProfileID = SelectedProfiles.ProfileID
... -- Add your paging here

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

1 голос
/ 17 июня 2011

Как отдельный запрос, я не могу придумать разумного способа оптимизации этого.

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

WITH
  filter AS (
SELECT
  [a].*
FROM
  (SELECT * FROM Profile WHERE @a IS NULL OR a = @a) AS [a]
INNER JOIN
  (SELECT id FROM Profile WHERE b = @b UNION ALL SELECT NULL WHERE @b IS NULL) AS [b]
    ON ([a].id = [b].id) OR ([b].id IS NULL)
INNER JOIN
  (SELECT id FROM Profile WHERE c = @c UNION ALL SELECT NULL WHERE @c IS NULL) AS [c]
    ON ([a].id = [c].id) OR ([c].id IS NULL)
.
.
.
INNER JOIN
  (SELECT id FROM Profile WHERE zz = @zz UNION ALL SELECT NULL WHERE @zz IS NULL) AS [zz]
    ON ([a].id = [zz].id) OR ([zz].id IS NULL)
)
, TempResult as (        
SELECT
  ROW_NUMBER() OVER(ORDER BY @sortColumn DESC) as RowNum,
  [filter].*
FROM
  [filter]
)
SELECT
  *
FROM
  TempResult
WHERE 
      (RowNum >= @FirstRow)
  AND (RowNum <= @LastRow)

РЕДАКТИРОВАТЬ

Кроме того, подумав об этом, вы можете дажеполучить тот же результат, просто имея 60 отдельных индексов.SQL Server может выполнять ИНДЕКСНЫЕ СЛИЯНИЯ ...

0 голосов
/ 27 июня 2011

Это классическая проблема запроса «Фильтр SQL». Я обнаружил, что типичные подходы "( @ b - ноль или b = @b)" и его обычные производные все дают посредственную производительность. Предложение OR имеет тенденцию быть причиной.

За прошедшие годы я много сделал для оптимизации производительности и настройки запросов. Подход, который я нашел наилучшим, заключается в генерации динамического SQL внутри хранимого процесса. В большинстве случаев вам также необходимо добавить «with Recompile» в оператор. Stored Proc помогает снизить вероятность атак с использованием SQL-инъекций. Перекомпиляция необходима для принудительного выбора индексов, соответствующих параметрам, которые вы ищете. Как правило, это по крайней мере на порядок быстрее.

Я согласен, вы также должны посмотреть на пункты, упомянутые выше, как: -

  1. Если вы обычно ссылаетесь только на небольшое подмножество столбцов, вы можете создавать некластеризованные индексы «Covering».

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

  3. Если многие столбцы имеют очень небольшое количество значений, рассмотрите возможность использования типа данных BIT. ИЛИ Создайте свой собственный BITMASKED BIGINT, чтобы представить множество столбцов, т. Е. Форму "Enumerated datatyle". Но будьте осторожны, так как любая функция в предложении WHERE (например, MOD или побитовое И / ИЛИ) не позволит оптимизатору выбрать индекс. Это работает лучше всего, если вы знаете значение для каждого и можете объединить их, чтобы использовать запрос равенства или диапазона.

  4. Хотя часто бывает полезно найти RoWID с небольшим запросом, а затем присоединиться, чтобы получить все остальные столбцы, которые вы хотите получить. (Как вы делаете выше) Этот подход иногда может иметь неприятные последствия. Если 1-я часть запроса выполняет сканирование индекса индекса, то часто бывает проще получить другие необходимые столбцы в списке выбора и сохранить 2-е сканирование таблицы. Так что всегда хорошо попробовать оба способа и посмотреть, что работает лучше всего.

  5. Не забудьте запустить SET STATISTICS IO ON и SET STATISTICS TIME ON. Перед запуском ваших тестов. Затем вы можете увидеть, где находится IO, и это может помочь вам в выборе индекса для более частой комбинации параметров. Я надеюсь, что это имеет смысл без длинных примеров кода. (это на моей другой машине)

0 голосов
/ 17 июня 2011

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

Но я думаю, что ваша самая важная проблема заключается в том, что у вас нет необходимости присоединяться:

SELECT profile.* FROM TempResult
WHERE 
     (RowNum >= @FirstRow)
     AND 
     (RowNum <= @LastRow)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...