SQL Server не будет использовать мой индекс - PullRequest
6 голосов
/ 09 июля 2009

У меня довольно простой запрос:

SELECT
     col1,
     col2…
FROM
     dbo.My_Table
WHERE
     col1 = @col1 AND
     col2 = @col2 AND
     col3 <= @col3

Это работало ужасно, поэтому я добавил индекс для col1, col2, col3 (int, bit и datetime). Когда я проверял план запроса, он игнорировал мой индекс. Я пытался изменить порядок столбцов в индексе во всех возможных конфигурациях, и он всегда игнорировал индекс. Когда я запускаю запрос, он выполняет сканирование кластерного индекса (размер таблицы находится в диапазоне от 700 до 800 Кбайт) и занимает 10-12 секунд. Когда я заставляю его использовать мой индекс, он мгновенно возвращается. Я был осторожен, чтобы очистить кэш и буферы между тестами.

Другие вещи, которые я пробовал:

UPDATE STATISTICS dbo.My_Table

CREATE STATISTICS tmp_stats ON dbo.My_Table (col1, col2, col3) WITH FULLSCAN

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

РЕДАКТИРОВАТЬ: один из возвращаемых столбцов является столбцом TEXT, поэтому использование индекса покрытия или INCLUDE не будет работать: (

Ответы [ 8 ]

13 голосов
/ 10 июля 2009

У вас есть 800k строк, проиндексированных col1, col2, col3. Col2 немного, поэтому его селективность составляет 50%. Col3 - это проверенный диапазон (<=), поэтому его селективность тоже будет примерно на 50%. Который оставляет кол1. Запрос составлен для общего параметризованного плана, поэтому он должен учитывать общий случай. Если у вас есть 10 различных значений col1, то ваш индекс вернет приблизительно 800k / 10 * 25%, что составляет ~ 20k ключей для поиска в кластеризованном индексе для получения части '...'. Если у вас есть 10 000 различных значений col1, то индекс вернет только 20 ключей для поиска. Как видите, важно не то, как вы строите свой индекс в этом случае, а фактические данные. В зависимости от избирательности col1 оптимизатор выберет план на основе сканирования кластерного индекса (лучше, чем 20 тыс. Ключевых поисков, каждый поиск стоит <em>по крайней мере 3-5 просмотров страниц) или один на основе на некластеризованном индексе (если col1 достаточно избирателен). В реальной жизни распределение col1 также играет роль, но это слишком усложнит объяснение.

Вы можете воспользоваться ретроспективным прогнозом и заявить, что план неверен, но план является наилучшей оценкой стоимости, основанной на данных, доступных на момент компиляции. Вы можете повлиять на него с помощью подсказок (индексная подсказка, как вы предлагаете, или оптимизировать подсказки, как подсказывает Кассной), но тогда ваш запрос может работать лучше для вашего набора тестов и намного хуже для другого набора данных, например, для случая, когда @ col1 = <the value that matches 500k records>. Вы также можете создать индексное покрытие, исключив, таким образом, «...» в списке проекций, который требует поиска кластеризованного индекса, и в этом случае некластеризованный индекс всегда лучше соответствует стоимости, чем кластеризованное сканирование.

Кимберли Трипп (Kimberley Tripp) имеет статью в блоге, посвященную этой теме, она называет ее «переломным моментом индекса », в котором объясняется, почему игнорируется явно превосходный индекс кандидата: некластеризованный индекс, который не распространяется на список проекций и имеет плохую селективность будет рассматриваться как более дорогостоящий, чем кластерное сканирование.

2 голосов
/ 09 июля 2009

SQL Server оптимизатор не подходит для оптимизации запросов, использующих переменные.

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

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

Вы также можете попытаться дать более легкую подсказку:

OPTION (OPTIMIZE FOR (@col1 = 1, @col2 = 0, @col3 = '2009-07-09'))

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

1 голос
/ 10 июля 2009

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

Или вместо него используется другой индекс?

1 голос
/ 10 июля 2009

Попробуйте замаскировать свои параметры, чтобы предотвратить перехват параметров:

CREATE PROCEDURE MyProc AS
    @Col1 INT
    -- etc...
AS
    DECLARE @MaskedCol1 INT
    SET @MaskedCol1 = @Col1
    -- etc...

    SELECT
         col1,
         col2…
    FROM
         dbo.My_Table
    WHERE
         col1 = @MaskecCol1 AND
         -- etc...

Звучит глупо, но я видел, как SQL-сервер делает некоторые странные вещи из-за перехвата параметров.

1 голос
/ 10 июля 2009

Вы пытались выбросить бит из индекса?

create index ix1 on My_Table(Col3, Col1) INCLUDE(Col2) 
-- include other columns from the select list if needed

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

1 голос
/ 09 июля 2009

Порядок индекса важен для этого запроса:

CREATE INDEX MyIndex ON MyTable (col3 DESC, col2 ASC, col1 ASC)

Это не столько ASC / DESC, сколько то, что когда sql-сервер идет в соответствие с предложением where, он может сначала совпадать с col3 и пройти индекс по этому значению.

0 голосов
/ 13 июля 2009

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

Если у вас есть столбец TEXT, попробуйте переключить тип данных на VARCHAR (MAX) и включить значения в некластеризованный индекс.

0 голосов
/ 10 июля 2009

Обнуляются ли столбцы? Иногда Sql Server считает, что ему нужно сканировать таблицу, чтобы найти значения NULL.

Попробуйте добавить в запрос "and col1 is not null", так как это заставит sqlserver использовать индекс без подсказки.

Кроме того, проверьте, действительно ли статистика актуальна:

SELECT 
    object_name = Object_Name(ind.object_id),
    IndexName = ind.name,
    StatisticsDate = STATS_DATE(ind.object_id, ind.index_id)
FROM SYS.INDEXES ind
order by STATS_DATE(ind.object_id, ind.index_id) desc
...