Как заставить лучший план выполнения, когда база данных форсирует объединение? - PullRequest
3 голосов
/ 26 июня 2019

Я оптимизирую запрос на SQL Server 2005. У меня есть простой запрос к mytable, который содержит около 2 миллионов строк:

SELECT id, num
FROM mytable
WHERE t_id = 587

Поле id представляет собой первичный ключ (кластеризованный индекс) и существует некластеризованный индекс в поле t_id.

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

Если я изменю запрос на следующий, сервер выполняет только 6 операций чтения страницы и только один поиск индекса без объединения:

SELECT id
FROM mytable
WHERE t_id = 587

Я попытался добавить индекс для столбца num и индекс для num и tid.Сервер не выбрал ни один индекс.

Я стремлюсь уменьшить количество чтений страниц, но все еще извлекаю столбцы id и num.

Ответы [ 3 ]

2 голосов
/ 26 июня 2019

Следующий индекс должен быть оптимальным:

CREATE INDEX idx ON MyTable (t_id)
INCLUDE (num)

Я не могу вспомнить, был ли столбец INCLUDEd действительным синтаксисом в 2005 году, возможно, вам придется использовать:

CREATE INDEX idx ON MyTable (t_id, num)

Столбец [id] будет включен в индекс, поскольку он является кластеризованным ключом.

1 голос
/ 26 июня 2019

Давайте разберем проблему, а затем обсудим решения для ее улучшения:

У вас есть таблица (назовем ее tblTest1 и содержит 2М записей) с кластеризованным индексом на id и неКластерный индекс на t_id, и вы собираетесь запросить данные, которые фильтруют данные, используя некластерный индекс и получить столбцы id и num.

Таким образом, сервер SQL будет использовать некластерный индексдля фильтрации данных (t_id=587), но после фильтрации данных SQL-серверу необходимо получить значения, хранящиеся в столбцах id и num.Очевидно, поскольку у вас есть кластеризованный индекс, SQL-сервер будет использовать этот индекс для получения данных, хранящихся в столбцах id и num.Это происходит потому, что листы в дереве некластеризованного индекса содержат значение кластерного индекса, поэтому вы видите оператор Key Lookup в плане выполнения.Фактически SQL Server использует Index seek(NonCluster) для поиска t_id=587, а затем использует Key Lookup для получения данных num! (SQL Server не будет использовать этот оператор для получения значения, хранящегося в столбце id, посколькуу вас есть кластеризованный индекс, а листы в некластеризованном индексе содержат значение кластерного индекса).

enter image description here

Обратимся к вышеупомянутому снимку экрана, когда мыIndex Seek(NonClustred) и Key Lookup, SQL Server требуется оператор Nested Loop Join для получения данных в столбце num с помощью оператора Index Seek(Nonclustered).Фактически на этом этапе SQL Server имеет два отдельных набора: один - это результаты, полученные из дерева некластеризованного индекса, а другой - данные внутри дерева кластерного индекса.

Исходя из этой истории, проблема ясна!Что произойдет, если мы скажем SQL-серверу не выполнять поиск ключей?это приведет к тому, что SQL Server выполнит запрос более коротким способом (нет необходимости в поиске ключей и, очевидно, нет необходимости в присоединении к вложенному циклу!).

Для этого нам нужно INCLUDE столбец num внутри дерева некластеризованного индекса, поэтому в этом случае лист этого индекса будет содержать данные столбца id, а также numданные столбца!Ясно, что когда мы говорим SQL Server найти данные с использованием NonClustred Index и вернуть столбцы id и num, ему не нужно выполнять поиск ключей!

Наконец, нам нужно сделать следующее:INCLUDE num в некластеризованном индексе!Благодаря @ MJH ответу:

CREATE NONCLUSTERED INDEX idx ON tblTest1 (t_id)
INCLUDE (num)

enter image description here

К счастью, SQL Server 2005 предоставил новую функцию для некластеризованных индексоввозможность включения дополнительных неключевых столбцов на конечный уровень некластеризованных индексов!

Подробнее:

https://www.red -gate.com / simple-talk /sql / learn-sql-сервер / использование-покрытия-индексов-для-улучшения-производительности запросов /

https://docs.microsoft.com/en-us/sql/relational-databases/indexes/create-indexes-with-included-columns?view=sql-server-2017

Но что произойдет, если мы напишем запрос какэто?

SELECT id, num
FROM tblTest1 AS t1
WHERE 
EXISTS (SELECT 1
              FROM tblTest1 t2
              WHERE t2.t_id = 587 AND t2.id = t1.id
             )

Это отличный подход, но давайте посмотрим план выполнения:

enter image description here

Очевидно, что SQL-сервер нуждаетсявыполнить поиск по индексу (NonClustered), чтобы найти t_id = 587, а затем получить данные из Clustered Index с помощью поиска Clustered Index Seek.В этом случае мы не получим заметного улучшения производительности.

Примечание: Когда вы используете индексы, вам нужен соответствующий план для их обслуживания.По мере фрагментации индексов их влияние на производительность запросов будет уменьшаться, и через некоторое время у вас могут возникнуть проблемы с производительностью!У вас должен быть соответствующий план их реорганизации и восстановления, когда они фрагментированы!

Подробнее: https://docs.microsoft.com/en-us/sql/relational-databases/indexes/reorganize-and-rebuild-indexes?view=sql-server-2017

1 голос
/ 26 июня 2019

Оптимальный индекс будет на (t_id, num, id).

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

SELECT t.id, t.num
FROM mytable t
WHERE EXISTS (SELECT 1
              FROM my_table t2
              WHERE t2.t_id = 587 AND t2.id = t.id
             );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...