Оптимальная стратегия для снижения производительности операций поиска — SQL Server 2008

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

[user] (approx. 40,000 rows, 37 cols):

id               INT (pk)
content_group_id INT (fk)
[username]       VARCHAR(20)
...

[content_group] (approx. 200,000 rows, 5 cols):

id    INT (pk)
title VARCHAR(20)
...

[content] (approx. 1,000,000 rows, 12 cols):

id                  INT (pk)
content_group_id    INT (fk)
content_type_id     INT (fk)
content_sub_type_id INT (fk)
...

[content_type] (2 rows, 3 cols)

id INT (pk)
...

[content_sub_type] (8 rows, 3 cols)
id              INT (pk)
content_type_id INT (fk)
...

Мы ожидаем, что количество этих строк значительно возрастет (в частности, таблицы user, content_group и content). Да, в пользовательской таблице довольно много столбцов, и мы определили некоторые из них, которые можно переместить в другие таблицы. Есть также куча индексов, которые мы применили к затронутым таблицам, которые помогли.

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

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

Итак, какие у нас есть варианты? Мы можем придумать несколько, но все они имеют свои плюсы и минусы:

Денормализация структуры таблицы

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

Плюсы:

  • Присоединение к таблице содержимого будет более оптимальным с использованием ее первичного ключа.

Минусы:

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

Больше денормализации структуры таблицы

Просто продублируйте нужные нам поля из таблицы контента в пользовательскую таблицу напрямую.

Плюсы:

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

Минусы

  • То же, что и выше: дополнительные поля для поддержки в пользовательской таблице, изменения в SQL и коде веб-сайта.

Создайте слой индексации среднего уровня

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

Плюсы:

  • Это хорошее долгосрочное решение. Lucene существует для повышения производительности поисковых систем.

Минусы:

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

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

Есть ли какие-то другие подходы, которые могли бы работать для нас? Есть ли какие-либо дополнительные плюсы и/или минусы в подходах, которые я изложил выше, которые могут повлиять на наши решения?


person Zac Seth    schedule 10.10.2011    source источник
comment
Можете ли вы опубликовать один из этих медленных запросов (плюс план выполнения)?   -  person Bogdan Sahlean    schedule 10.10.2011
comment
Кроме того, какая версия SQL Server?   -  person Bogdan Sahlean    schedule 10.10.2011
comment
Я могу сказать вам, что мы используем SQL Server 2008, однако после обсуждения с коллегами мы решили, что фактический запрос содержит детали, которые слишком конфиденциальны для публикации. Что я могу вам сказать, так это то, что основная часть стоимости запроса приходится на поиск некластеризованного индекса из таблицы контента с использованием content_sub_type_id. За этим следует хэш-сопоставление content_group_id с таблицей контента — на них приходится 75% падения производительности. Третьей дорогостоящей операцией является предложение != по отношению к полю id пользовательской таблицы. Извините, я не могу быть более конкретным, чем это.   -  person Zac Seth    schedule 10.10.2011


Ответы (1)


поиск некластеризованного индекса из таблицы содержимого с использованием идентификатора content_sub_type_id. За этим следует совпадение хэша для content_group_id с таблицей содержимого.

Это описание указывает на то, что ваш ресурсоемкий запрос фильтрует таблицу content на основе полей из content_type:

select ...
from content c
join content_type ct on c.content_type_id = ct.id
where ct.<field> = <value>;

Этот дизайн таблицы и возникающая в результате проблема, которую вы только что видели, на самом деле довольно распространены. Проблемы возникают в основном из-за очень низкой избирательности таблиц поиска (content_type имеет 2 строки, поэтому избирательность content_type_id в контенте наверное 50%, огромная). Есть несколько решений, которые вы можете попробовать:

1) Организуйте таблицу content в кластеризованном индексе с content_type_id в качестве ведущего ключа. Это позволит объединению выполнять сканирование диапазона, а также избежать поиска ключа/закладки для полноты проекции. Поскольку изменение кластеризованного индекса повлияет на другие запросы, его необходимо тщательно протестировать. Очевидно, что первичный ключ на content должен быть применен с некластеризованным ограничением.

2) Предварительно прочитать значение content_type_id, а затем сформулировать запрос без соединения между content и content_type:

select ...
from content c
where c.content_type_id = @contentTypeId;

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

3) Денормализация content_Type в контент. Вы упомянули денормализацию, но ваше предложение о денормализации контента в пользователей не имеет для меня особого смысла. Отбросьте таблицу content_type, вставьте поля content_type в саму таблицу content и живите со всеми проблемами денормализации.

4) Предварительное соединение в материализованном представлении. Вы говорите, что уже пробовали это, но я сомневаюсь, что вы пробовали правильное материализованное представление. Вы также должны понимать, что только редакция Enterprise использует индекс материализованного представления автоматически, для всех остальных редакций требуется NOEXPAND подсказка:

create view vwContentType 
with schemabinding
as 
select content_type_id, content_id
from dbo.content c
join dbo.content_type_id ct on c.content_type_id = ct.content_type_id;

create unique clustered index cdxContentType on vwContentType (content_type_id, content_id);

select ...
from content c
join vwContentType ct with (noexpand)
on ct.content_id = c.content_id
where ct.content_type_id = @contentTypeId;

Решения 2), 3) и 4) в основном академические. Учитывая очень низкую избирательность content_type_id, у вас есть единственное решение, которое имеет шанс, — сделать его ведущим ключом в кластеризованном индексе content. Я не расширил анализ до content_Sub_type, но готов поспорить, что всего с 8 строками у него та же проблема, что потребовало бы также вставить его в кластеризованный индекс (возможно, в качестве второго ведущего ключа).

person Remus Rusanu    schedule 10.10.2011
comment
Ремус, спасибо за ответ. Сегодня утром я изучал вариант 1, и он выглядит многообещающе. Мой вопрос: было бы неплохо добавить content_group_id в кластеризованный индекс? Согласно searchsqlserver.techtarget. com/tip/ По сути, идентификаторы группы, типа и подтипа представляют собой составной первичный ключ, хотя в настоящее время он не является явным. Прав ли я, предполагая, что при таком подходе будет высокая фрагментация таблицы содержимого? - person Zac Seth; 11.10.2011
comment
Итак, я применил кластеризованный индекс к упомянутым выше ключам — основываясь на вашем первоначальном предложении для Варианта 1, и мы видим сокращение времени обработки на 50% — вы были правы. Большое спасибо, Ремус! Проголосовал и принял... - person Zac Seth; 11.10.2011
comment
@Zac: фрагментация действительно увеличится, однако фрагментация — это то, что можно решить с помощью операций реорганизации / перестройки, когда это необходимо. - person Remus Rusanu; 11.10.2011
comment
@Zac: уменьшение на 50%, потому что полное сканирование таблицы, вероятно, было заменено сканированием диапазона для одного из двух типов контента. Это все еще очень много, так как вы обнаружите, что сканирование половины таблицы неприемлемо. Но я надеюсь, что у вас есть идея, и вы и ваша команда можете улучшить ее дальше. Эксперимент. И прочтите все здесь: msdn.microsoft.com/en-us/library/ms190804 .aspx - person Remus Rusanu; 11.10.2011
comment
Спасибо, я ценю помощь, Ремус. Я думаю, что уменьшение на 50%, вероятно, связано с sub_type, поскольку типы контента примерно на 95% склоняются к одному значению, тогда как подтипы склоняются к 2 значениям. Но да, я думаю, что теперь я понимаю принцип. Спасибо за ссылку тоже. - person Zac Seth; 12.10.2011