Почему медленное выполнение top (1) для индексированного столбца в SQL Server? - PullRequest
20 голосов
/ 16 марта 2010

Я озадачен следующим. У меня есть БД с примерно 10 миллионами строк, и (среди прочих индексов) на 1 столбце (campaignid_int) есть индекс.

Теперь у меня есть 700 тыс. Строк, где кампания действительно равна 3835

.

Для всех этих строк, connectionid одинаков.

Я просто хочу узнать этот connectionid.

 use messaging_db;
 SELECT     TOP (1) connectionid
 FROM         outgoing_messages WITH (NOLOCK)
 WHERE     (campaignid_int = 3835)

Теперь выполнение этого запроса занимает около 30 секунд!

Я (с моим небольшим знанием БД) ожидал, что он возьмет любую из строк и вернет мне этот connectionid

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

Как бы я занялся этим и почему это не работает?

редактирование:

estimated execution plan:

select (0%) - top (0%) - clustered index scan (100%)

Ответы [ 8 ]

17 голосов
/ 16 марта 2010

Из-за статистики вы должны явно попросить оптимизатор использовать созданный вами индекс вместо кластерного.

SELECT  TOP (1) connectionid
FROM    outgoing_messages WITH (NOLOCK, index(idx_connectionid))
WHERE  (campaignid_int = 3835)

Я надеюсь, что это решит проблему.

С уважением, Enrique

9 голосов
/ 22 ноября 2012

У меня недавно была та же проблема, и ее действительно довольно просто решить (по крайней мере, в некоторых случаях).

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

4 голосов
/ 16 марта 2010

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

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

1 голос
/ 16 марта 2010

Индекс может быть бесполезным по 2 причинам:

  • 700 КБ в 10 миллионах может быть недостаточно избирательным
  • и / или
  • необходимо указать connectionid, чтобы весь запрос мог использовать только индекс

В противном случае оптимизатор решит, что он также может использовать PK / кластерный индекс для фильтрации по CampaignID_int и получения connectionid, чтобы избежать поиска закладок на 700 тыс. Строк из текущего индекса.

Итак, я предлагаю это ...

CREATE NONCLUSTERED INDEX IX_Foo ON MyTable (campaignid_int) INCLUDE (connectionid)
1 голос
/ 16 марта 2010

Если столбец campaignid_int не проиндексирован, добавьте в него индекс. Это должно ускорить запрос. Прямо сейчас я предполагаю, что вам нужно выполнить полное сканирование таблицы, чтобы найти совпадения для campaignid_int = 3835, прежде чем будет возвращена строка top(1) (фильтрация происходит до того, как будут возвращены результаты).

РЕДАКТИРОВАТЬ: Индекс уже существует, но поскольку SQL Server выполняет сканирование кластерного индекса, оптимизатор проигнорировал индекс. Вероятно, это связано с (многими) повторяющимися строками с одинаковым значением campaignid_int. Вы должны рассмотреть возможность индексации по-другому или запросить другой столбец, чтобы получить connectionid, который вы хотите.

0 голосов
/ 16 марта 2010

но так как я указываю 'top (1)', значит: дай мне любой ряд. С чего бы это сначала ползти по 700к строк просто вернуть один? - Reinier 30 минут назад

Извините, пока не могу комментировать, но ответ здесь заключается в том, что SQL-сервер не поймет человеческого эквивалента "Принеси мне первое, что найдешь", когда услышит "Top 1". Вместо ожидаемого «Дайте мне любую строку» SQL Server отправляет и выбирает первую из всех найденных строк. Только раз он знает, что сначала выбираются все строки, а затем отбрасываются остальные. Очень тщательно, но в вашем случае не очень быстро.

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

0 голосов
/ 16 марта 2010

Это не отвечает на ваш вопрос, но попробуйте использовать:

SET ROWCOUNT 1
SELECT     connectionid
 FROM         outgoing_messages WITH (NOLOCK)
 WHERE     (campaignid_int = 3835)

Я видел, что top (x) также очень плохо работает в определенных ситуациях. Я уверен, что он делает полное сканирование таблицы. Возможно, ваш индекс по этому конкретному столбцу необходимо перестроить? Однако вышесказанное стоит попробовать.

0 голосов
/ 16 марта 2010

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

Возможные решения? Сделайте индекс составным, если у вас есть что-то, с чем его можно составить, например, составьте дату, когда сообщение было отправлено (если я правильно понимаю ваш случай), а затем выберите первую запись из списка с указанным номером, упорядоченным по дате. Хотя я не уверен, будет ли это лучше (например, составной индекс занимает больше места) - только предположение.

РЕДАКТИРОВАТЬ: Я только что опробовал предложение сделать составной индекс, добавив столбец даты. Если вы сделаете это и укажете order by date в своем запросе, поиск по индексу будет выполнен, как ожидается.

...