Как оптимизировать индексы для занятой таблицы? - PullRequest
4 голосов
/ 10 января 2010

у меня есть эта таблица комментариев (чуть более 1 миллиона строк), которая получает около 10.000 вставок и около 100.000 запросов к ней каждый день, а также незначительные удаления и обновления. запрос, который получает комментарии, вызывает проблемы с производительностью, которые иногда блокируют всю базу данных, и я получаю много тайм-аутов. Пожалуйста, помогите мне настроить мои индексы и что-нибудь еще, чтобы он работал лучше. ниже я включил информацию об этом, если вам нужно больше, пожалуйста, спросите. я ежедневно перестраиваю все индексы и запускаю веб-версию sql server 2008 на сервере 2008.

спасибо:)

структура:

id (int, identity)
profile_id (int)
owner_id (int)
added_date (datetime)
comments varchar(4000)
logical_delete (datetime)

индексы:

id (PK, clustered)
profile_id (70% fill)
owner_id (70% fill)
added_date (70% fill)
profile_id + logical_delete (70%)

запрос:

    select 
        c.id, c.owner_id, c.comments, c.is_public, c.added_date, 
        u.first_name, u.last_name, c.profile_id
    from [profile_comment] c with(nolock) 
    inner join [user] u with(nolock) on u.id = c.owner_id 
    where c.profile_id = @profile_id and c.logical_delete is null
    order by c.added_date desc 

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

  |--Nested Loops(Inner Join, OUTER REFERENCES:([c].[owner_id], [Expr1005]) WITH ORDERED PREFETCH)
       |--Sort(ORDER BY:([c].[added_date] DESC)) **[5%]**
       |    |--Nested Loops(Inner Join, OUTER REFERENCES:([c].[id], [Expr1004]) WITH UNORDERED PREFETCH) **[0%]** 
       |         |--Index Seek(OBJECT:([DB].[dbo].[profile_comment].[IX_profile_comment_combined1] AS [c]), SEEK:([c].[profile_id]=(1) AND [c].[logical_delete]=NULL) ORDERED FORWARD) **[1%]**
       |         |--Clustered Index Seek(OBJECT:([JakLeci].[dbo].[profile_comment].[PK__profile_comment__primary] AS [c]), SEEK:([c].[id]=[JakLeci].[dbo].[profile_comment].[id] as [c].[id]) LOOKUP ORDERED FORWARD) **[47%]**
       |--Clustered Index Seek(OBJECT:([DB].[dbo].[user].[PK__user__id] AS [u]), SEEK:([u].[id]=[DB].[dbo].[profile_comment].[owner_id] as [c].[owner_id]) ORDERED FORWARD)  **[47%]**

Ответы [ 4 ]

1 голос
/ 10 января 2010
Nested Loops(Inner Join, OUTER REFERENCES:([c].[owner_id], [Expr1005]) WITH ORDERED PREFETCH)
       |--Sort(ORDER BY:([c].[added_date] DESC)) **[5%]**
       |    |--Nested Loops(Inner Join, OUTER REFERENCES:([c].[id], [Expr1004]) WITH UNORDERED PREFETCH) **[0%]** 
       |         |--Index Seek(OBJECT:([DB].[dbo].[profile_comment].[IX_profile_comment_combined1] AS [c]), SEEK:([c].[profile_id]=(1) AND [c].[logical_delete]=NULL) ORDERED FORWARD) **[1%]**
       |         |--Clustered Index Seek(OBJECT:([JakLeci].[dbo].[profile_comment].[PK__profile_comment__primary] AS [c]), SEEK:([c].[id]=[JakLeci].[dbo].[profile_comment].[id] as [c].[id]) LOOKUP ORDERED FORWARD) **[47%]**
       |--Clustered Index Seek(OBJECT:([DB].[dbo].[user].[PK__user__id] AS [u]), SEEK:([u].[id]=[DB].[dbo].[profile_comment].[owner_id] as [c].[owner_id]) ORDERED FORWARD)  **[47%]**

Вот как я читаю этот план: запрос начинается с поиска для profile_id = @profile_id, и значение логического_деления равно нулю в IX_Profile_comment_combined, затем он выполняет вложенный цикл соединения по кластерному индексу, сортирует результат по добавленной дате и это делает вложенный цикл для пользователя.

Одна вещь, которую вы могли бы быстро устранить, это SORT, изменив определение IX_profile_combined на:

CREATE INDEX IX_profile_combined
 ON profile_comment(logical_deleted, profile_id, added_date)

Поскольку логический_деленный столбец с очень низкой избирательностью, он должен быть самым левым ключом в индексе. Поскольку выходные данные для определенного @profile_id должны быть упорядочены по добавленной дате, тогда добавленная дата должна быть справа от profile_id в ключевом порядке.

Кроме сортировки, план запроса выглядит хорошо для меня. Но мне довольно любопытно, почему запрос, который, предположительно, является самым большим боровом в системе, потребляет всего 1% при получении всей строки-кандидата, затем удручает до 93% времени при двух поисках кластерного индекса и только 5% при сортировке. Это не является признаком проблемного запроса. Является ли @profile_id ключом с очень низкой избирательностью? Собранный вами план собран из нерепрезентативного прогона, который показал себя хорошо?

1 голос
/ 10 января 2010

Кластерный индекс на (profile_id, Added_date DESC) должен сделать свое дело. Это даст вам быстрый поиск по profile_id, уже отсортированному по добавленной дате. Единственными оставшимися операциями будут фильтрация по логическому_детю и присоединение цикла к пользователю (которое должно быть кластеризовано по ИД_пользователя).

В зависимости от количества возвращаемых строк, вы все равно могли бы читать с диска немного. Колонка ваших комментариев довольно широка. Возможно, вы захотите ограничить количество строк, возвращаемых add_date (или TOP), или кэшировать результаты.

Я не могу представить, что это вызывает высокую загрузку процессора, а вы используете NOLOCK, поэтому вам не следует блокировать другие запросы. Если это действительно причина ваших тайм-аутов, то это должен быть ввод / вывод. Вы можете проверить использование памяти и дисковой подсистемы, чтобы убедиться, что вы получаете приличную производительность. Проверьте логическое чтение и время процессора до и после, чтобы определить, помогаете ли вы.

Возможно, вы могли бы также отбросить некоторые ваши индексы для ускорения вставок. Я не уверен, что 70% -ое заполнение делает что-то иное, чем трата пространства, но я могу ошибаться.

0 голосов
/ 10 января 2010

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

(profile_id, is_deleted, owner_id, added_date desc)

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

если это не вариант, следующим лучшим вариантом будет создание многостолбцового индекса покрытия, включающего:

(profile_id, is_deleted, owner_id, added_date desc, id, comments, is_public)

, поскольку комментарии довольно велики, вы можете включать комментарии в качестве неключевого столбца, используя синтаксис INCLUDE:

create index idx on c (profile_id, is_deleted, owner_id, added_date desc, id,
  is_public) include (comments)

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

подробнее здесь:

http://www.sql -server-performance.com / подсказки / * covering_indexes_p1.aspx 1018 ** * 1019

и, конечно, u.id должен быть первичным ключом для пользователей.

лучше всего сделать is_deleted ненулевым (tinyint по умолчанию равно 0).

с NOLOCK / READUNCOMMITTED, выбор не создаст никаких блокировок или блоков.

0 голосов
/ 10 января 2010

Почему бы вам не выполнить свой запрос в Management Studio и не показать фактический план выполнения, чтобы попытаться выяснить, в чем проблема?

Без этого единственное, что я мог бы предложить, - это НЕ делать ваш первичный идентификационный ключ кластеризованным индексом, если только вы регулярно не запрашиваете об этом. Вы можете попытаться добавить индекс для profile_id и Added_date (или что-то еще, чтобы сделать его уникальным) и сделать его кластерным индексом, поскольку вы запрашиваете по profile_id и, вероятно, редко запрашиваете поле идентификации.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...