Почему это сканирование индекса, а не поиск индекса? - PullRequest
15 голосов
/ 30 июня 2011

Вот запрос:

SELECT      top 100 a.LocationId, b.SearchQuery, b.SearchRank
FROM        dbo.Locations a
INNER JOIN  dbo.LocationCache b ON a.LocationId = b.LocationId
WHERE       a.CountryId = 2
AND         a.Type = 7

Индексы местоположения:

PK_Locations:

LocationId

IX_Locations_CountryId_Type:

CountryId, тип

Индексы LocationCache:

PK_LocationCache:

LocationId

IX_LocationCache_LocationId_SearchQuery_SearchRank:

LocationId, SearchQuery, SearchRank

План выполнения:

enter image description here

Итак, Index Seek на Locations, используя индекс покрытия, круто.

Но почему он выполняет индексное сканирование для индекса покрытия LocationCache?

Этот покрывающий индекс имеет LocationId, SearchQuery, SearchRank в индексе (не как «включенные столбцы»).

Наведите указатель мыши при сканировании индекса:

enter image description here

Этот запрос должен идти в индексированном представлении, обслуживаемом каталогом FTS SQL Server, используемым плагином автозаполнения, поэтому его необходимо оптимизировать на 100%.

В данный момент запрос занимает 3 секунды. Должно быть <0. </p>

Есть идеи?

Ответы [ 5 ]

32 голосов
/ 30 июня 2011

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

И он использует оператор Merge Join для реализации вашего INNER JOIN, потому что считает, что это будет быстрее, чем более типичный оператор Nested Loop Join.И это, вероятно, правильно (как правило, так и есть), используя два выбранных им индекса, он имеет входные потоки, которые предварительно отсортированы в соответствии с вашим условием соединения (LocationID).Когда входные потоки предварительно отсортированы, как это, то объединения слиянием почти всегда быстрее, чем два других (объединения петли и хэша).

Недостатком является то, что вы заметили: кажется, что сканирование всегоиндексировать, так как это может быть быстрее, если он читает так много записей, которые никогда не могут быть использованы?Ответ в том, что сканы (из-за их последовательной природы) могут читать в 10-100 раз больше записей в секунду, чем ищет.

Теперь поиск обычно побеждает, потому что он выборочный: он получает только те строки, которые выспросите, тогда как сканы неселективны: они должны возвращать каждую строку в диапазоне.Но поскольку у сканов скорость чтения на намного выше, они часто могут превысить скорость поиска, если отношение отброшенных строк к совпадающим строкам на ниже , чем отношение строк сканирования / с VS.Поиск строк / сек.

Вопросы?


ОК, меня попросили объяснить последнее предложение более подробно:

"Сброшенная строка" - это та, которуюСканирование читает (потому что он должен прочитать все в индексе), но это будет отклонено оператором Merge Join, потому что у него нет совпадения на другой стороне, возможно, потому что условие предложения WHERE уже исключило его.

«Соответствующие строки» - это те, которые он прочитал, которые фактически соответствуют чему-то в соединении слиянием.Это те же строки, которые были бы прочитаны поиском, если бы сканирование было заменено поиском.

Вы можете выяснить, что есть, посмотрев статистику в плане запросов.Видите эту огромную жирную стрелку слева от индексного сканирования?Это показывает, сколько строк оптимизатор считает, что он будет читать при сканировании.Поле статистики сканирования индекса, которое вы разместили, показывает, что возвращенные фактические строки составляют около 5,4 млн. (5 394 402).Это равно:

TotalScanRows = (MatchingRows + DiscardedRows)

(по-моему, в любом случае).Чтобы получить совпадающие строки, посмотрите на «Фактические строки», сообщенные оператором объединения слиянием (возможно, вам придется снять ТОП-100, чтобы получить это точно).Как только вы это узнаете, вы можете получить Отброшенные строки по:

DiscardedRows = (TotalScanRows - MatchingRows)

И теперь вы можете вычислить соотношение.

8 голосов
/ 30 июня 2011

Принимая во внимание, что это приведет к тому, что запрос может работать плохо, и когда в него будут внесены дополнительные изменения, использование INNER LOOP JOIN должно принудительно использовать индекс покрытия для dbo.LocationCache.

SELECT      top 100 a.LocationId, b.SearchQuery, b.SearchRank
FROM        dbo.Locations a
INNER LOOP JOIN dbo.LocationCache b ON a.LocationId = b.LocationId
WHERE       a.CountryId = 2
AND         a.Type = 7
4 голосов
/ 30 июня 2011

Вы пытались обновить свою статистику?

UPDATE STATISTICS dbo.LocationCache

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

http://social.msdn.microsoft.com/Forums/en-CA/sqldatabaseengine/thread/82f49db8-0c77-4bce-b26c-1ad0a4af693b

Резюме

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

Затем он решает, является ли он болееЭффективно искать вниз по индексу или сканировать весь конечный уровень индекса (в данном случае это касается прикосновения к каждой странице в таблице, потому что это кластерный индекс). Он делает это, рассматривая несколько вещей.Во-первых, он угадывает, сколько строк / страниц нужно будет сканировать.Это называется переломным моментом, и этот процент ниже, чем вы думаете.Посмотрите этот великий блог Кимберли Трипп http://www.sqlskills.com/BLOGS/KIMBERLY/category/The-Tipping-Point.aspx

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

Можно заставить SQL искать индекс, используя подсказку запроса FORCESEEK, но, пожалуйста, используйте это с осторожностью, так как обычно, если вы сохраняете все, что поддерживаете, SQL довольно хорош в определении наиболее эффективного плана !!

2 голосов
/ 16 января 2013

Короче говоря: у вас нет фильтра в LocationCache, все содержимое таблицы должно быть возвращено.У вас есть полностью закрывающий индекс.Индекс SCAN (один раз) - самая дешевая операция, и оптимизатор запросов выбирает ее.

Для оптимизации: вы объединяете целые таблицы, а позже получаете только 100 лучших результатов.Я не знаю, насколько они велики, но попробую выполнить запрос к таблице [Locations] CountryId, Type, а затем объединить только результат с [LocationCache].Будет быстрее, если у вас там будет более 1000 строк.Кроме того, попробуйте добавить еще несколько ограничительных фильтров перед объединениями, если это возможно.

Индексное сканирование: поскольку сканирование касается каждой строки в таблице независимо от того, соответствует ли оно критериям, стоимость пропорциональна общему количеству строк в таблице.Таким образом, сканирование является эффективной стратегией, если таблица небольшая или если большинство строк удовлетворяют предикату.

Поиск по индексу: поскольку поиск касается только тех строк, которые соответствуют требованиям, и страниц, которые содержат эти соответствующие строки,стоимость пропорциональна количеству соответствующих строк и страниц, а не общему количеству строк в таблице.

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

источник

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

Я сделал быстрый тест и придумал следующее

CREATE TABLE #Locations
(LocationID INT NOT NULL ,
CountryID INT NOT NULL ,
[Type] INT NOT NULL 
CONSTRAINT PK_Locations
        PRIMARY KEY CLUSTERED ( LocationID ASC )
)

CREATE NONCLUSTERED INDEX [LocationsIndex01] ON #Locations
(
    CountryID ASC,
    [Type] ASC
)

CREATE TABLE #LocationCache
(LocationID INT NOT NULL ,
SearchQuery VARCHAR(50) NULL ,
SearchRank INT NOT NULL 
CONSTRAINT PK_LocationCache
        PRIMARY KEY CLUSTERED ( LocationID ASC )

)

CREATE NONCLUSTERED INDEX [LocationCacheIndex01] ON #LocationCache
(
    LocationID ASC,
    SearchQuery ASC,
    SearchRank ASC
)

INSERT INTO #Locations
SELECT 1,1,1 UNION
SELECT 2,1,4 UNION
SELECT 3,2,7 UNION
SELECT 4,2,7 UNION
SELECT 5,1,1 UNION
SELECT 6,1,4 UNION
SELECT 7,2,7 UNION
SELECT 8,2,7 --UNION

INSERT INTO #LocationCache
SELECT 4,'BlahA',10 UNION
SELECT 3,'BlahB',9 UNION
SELECT 2,'BlahC',8 UNION
SELECT 1,'BlahD',7 UNION
SELECT 8,'BlahE',6 UNION
SELECT 7,'BlahF',5 UNION
SELECT 6,'BlahG',4 UNION
SELECT 5,'BlahH',3 --UNION

SELECT * FROM #Locations
SELECT * FROM #LocationCache

SELECT      top 3 a.LocationId, b.SearchQuery, b.SearchRank
FROM        #Locations a
INNER JOIN  #LocationCache b ON a.LocationId = b.LocationId
WHERE       a.CountryId = 2
AND         a.[Type] = 7

DROP TABLE #Locations
DROP TABLE #LocationCache

Для меня план запроса показывает поиск с внутренним объединением с вложенным циклом.Если вы запустите это, вы получите оба поиска?Если вы это сделаете, то проведите тестирование в вашей системе, создайте копию таблицы Locations и LocationCache и назовите их, скажем, Locations2 и LocationCache2 со всеми индексами, и скопируйте в них свои данные.Тогда попробуйте запрос, попавший в новые таблицы?

...