Мне нужно оптимизировать SELECT
оператор, но SQL Server всегда выполняет сканирование индекса, а не поиск. Это запрос, который, конечно, находится в хранимой процедуре:
CREATE PROCEDURE dbo.something
@Status INT = NULL,
@IsUserGotAnActiveDirectoryUser BIT = NULL
AS
SELECT [IdNumber], [Code], [Status], [Sex],
[FirstName], [LastName], [Profession],
[BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE (@Status IS NULL OR [Status] = @Status)
AND
(
@IsUserGotAnActiveDirectoryUser IS NULL
OR
(
@IsUserGotAnActiveDirectoryUser IS NOT NULL AND
(
@IsUserGotAnActiveDirectoryUser = 1 AND ActiveDirectoryUser <> ''
)
OR
(
@IsUserGotAnActiveDirectoryUser = 0 AND ActiveDirectoryUser = ''
)
)
)
И это индекс:
CREATE INDEX not_relevent ON dbo.Employee
(
[Status] DESC,
[ActiveDirectoryUser] ASC
)
INCLUDE (...all the other columns in the table...);
План:
Почему SQL Server выбрал сканирование? Как я могу это исправить?
Определения столбцов:
[Status] int NOT NULL
[ActiveDirectoryUser] VARCHAR(50) NOT NULL
Параметры состояния могут быть:
NULL: all status,
1: Status= 1 (Active employees)
2: Status = 2 (Inactive employees)
IsUserGotAnActiveDirectoryUser может быть:
NULL: All employees
0: ActiveDirectoryUser is empty for that employee
1: ActiveDirectoryUser got a valid value (not null and not empty)
@Status
?Status DESC
? Сколько значений существует, дляStatus
чего они (если число мало), и каждое значение представлено примерно одинаково? Покажите нам выводSELECT TOP (20) [Status], c = COUNT(*) FROM dbo.Employee GROUP BY [Status] ORDER BY c DESC;
Ответы:
Я не думаю, что сканирование вызвано поиском пустой строки (и хотя вы можете добавить отфильтрованный индекс для этого случая, это поможет только очень специфическим вариантам запроса). Скорее всего, вы стали жертвой перехвата параметров и единого плана, не оптимизированного для всех различных комбинаций параметров (и значений параметров), которые вы будете предоставлять для этого запроса.
Я называю это процедурой «кухонная раковина» , потому что вы ожидаете, что один запрос предоставит все вещи, включая кухонную раковину.
У меня есть видео о моем решении этого вопроса здесь и здесь, а также сообщение в блоге об этом , но, по сути, лучший опыт, который у меня есть для таких запросов:
OPTION (RECOMPILE)
- это предотвращает принудительное использование определенных значений параметров неправильного типа плана, особенно полезно, когда у вас есть искажение данных, плохая статистика или когда при первом выполнении оператора используется нетипичное значение, которое приведет к другому плану, чем позже и чаще казни.optimize for ad hoc workloads
- это предотвращает загрязнение кэша вашего плана вариантами запроса, которые используются только один раз.Включить оптимизацию для специальных рабочих нагрузок:
Измените вашу процедуру:
Если у вас есть рабочая нагрузка, основанная на этом наборе запросов, которые вы можете отслеживать, вы можете проанализировать выполнение и посмотреть, какие из них больше всего выиграют от дополнительных или разных индексов - вы можете сделать это с разных точек зрения, от простого "какой комбинации параметры предоставляются чаще всего? на "какие отдельные запросы имеют наибольшее время выполнения?" Мы не можем ответить на эти вопросы, основываясь только на вашем коде, мы можем только предположить, что любой индекс будет полезен только для подмножества всех возможных комбинаций параметров, которые вы пытаетесь поддерживать. Например, если
@Status
NULL, то поиск по этому некластерному индексу невозможен. Так что в тех случаях, когда пользователям нет дела до статуса, вы получите сканирование, если у вас нет индекса, который обслуживает другие предложения (но такой индекс также не будет полезен, учитывая вашу текущую логику запроса - либо пустая строка, либо не пустая строка не является точно селективной).В этом случае, в зависимости от набора возможных
Status
значений и от того, как распределены эти значения,OPTION (RECOMPILE)
может не потребоваться. Но если у вас есть некоторые значения, которые приведут к 100 строкам, и некоторые значения, которые приведут к сотням тысяч, вы можете захотеть это там (даже при стоимости процессора, которая должна быть незначительной, учитывая сложность этого запроса), так что вы можете получить ищет в максимально возможном количестве случаев. Если диапазон значений достаточно ограничен, вы можете даже сделать что-то хитрое с динамическим SQL, где вы скажете: «У меня есть это очень избирательное значение@Status
, поэтому, когда это конкретное значение передается, внесите это небольшое изменение в текст запроса, чтобы это считается другим запросом и оптимизировано для этого значения параметра. "источник
Отказ от ответственности : некоторые вещи в этом ответе могут заставить ДБА вздрогнуть. Я подхожу к этому с чистой точки зрения производительности - как получить индексный поиск, когда вы всегда получаете индексное сканирование.
С этим из пути, здесь идет.
Ваш запрос - это так называемый «запрос кухонной раковины» - один запрос, предназначенный для удовлетворения целого ряда возможных условий поиска. Если пользователь устанавливает
@status
значение, вы хотите фильтровать это состояние. Если@status
естьNULL
, вернуть все статусы и так далее.Это создает проблемы с индексацией, но они не связаны с гибкостью, потому что все ваши условия поиска соответствуют критериям.
Это саркастично
Это не sargable , поскольку SQL Server должен оценить
ISNULL([status], 0)
для каждой строки , вместо того , чтобы искать одно значение в индексе:Я воссоздал проблему с раковиной в более простой форме:
Если вы попробуете следующее, вы получите сканирование индекса, даже если A - первый столбец индекса:
Это, однако, приводит к поиску индекса:
Пока вы используете управляемое количество параметров (два в вашем случае), вы, вероятно, могли бы просто
UNION
связать поисковые запросы - в основном все перестановки критериев поиска. Если у вас есть три критерия, это будет выглядеть грязно, а с четырьмя это будет совершенно неуправляемо. Вы были предупреждены.Однако для того, чтобы третий из этих четырех использовал поиск по индексу, вам понадобится второй индекс
(B, A)
. Вот как ваш запрос может выглядеть с этими изменениями (включая мой рефакторинг запроса, чтобы сделать его более читабельным).... плюс вам понадобится дополнительный индекс
Employee
с обратными двумя столбцами индекса.Для полноты я должен упомянуть, что
x=@x
неявно означает, чтоx
не может быть,NULL
потому чтоNULL
никогда не равноNULL
. Это немного упрощает запрос.И да, динамический SQL-ответ Аарона Бертранда - лучший выбор в большинстве случаев (т. Е. Всякий раз, когда вы можете жить с перекомпиляциями).
источник
Ваш основной вопрос, кажется, «Почему», и я думаю, что вы могли бы найти ответ на минуте 55 или около того этой Великой презентации Адама Маханика на TechEd несколько лет назад.
Я упоминаю 5 минут на 55 минуте, но вся презентация того стоит. Если вы посмотрите на план запроса для вашего запроса, я уверен, что вы найдете в нем Остаточные предикаты для поиска. По сути, SQL не может «видеть» все части индекса, потому что некоторые из них скрыты неравенствами и другими условиями. Результатом является индексное сканирование для супернабора на основе предиката. Этот результат помещается в буфер и затем повторно сканируется с использованием остаточного предиката.
Проверьте свойства оператора сканирования (F4) и посмотрите, есть ли в списке свойств «Поиск предиката» и «Предикат».
Как указали другие, этот запрос трудно проиндексировать как есть. Недавно я работал над многими подобными, и каждому требовалось свое решение. :(
источник
Прежде чем мы зададимся вопросом, является ли поиск по индексу предпочтительным по сравнению со сканированием по индексу, одним из практических правил является проверка того, сколько строк возвращено по сравнению с общим числом строк базовой таблицы. Например, если вы ожидаете, что ваш запрос вернет 10 строк из 1 миллиона строк, тогда поиск по индексу, вероятно, будет более предпочтительным, чем сканирование по индексу. Однако, если несколько тысяч строк (или более) должны быть возвращены из запроса, тогда поиск по индексу НЕ обязательно может быть предпочтительным.
Ваш запрос не сложен, поэтому, если вы можете опубликовать план выполнения, у нас могут быть лучшие идеи, чтобы помочь вам.
источник
это просто оригинал отформатирован
это ревизия - не уверен на 100% в этом, но (возможно) попробую
хотя бы один ИЛИ, вероятно,
возникнет проблема, которая может привести к поломке ActiveDirectoryUser null
источник