2008-12-10 4 views
4

Я работаю над дизайном иерархической структуры базы данных, которая моделирует каталог, содержащий продукты (это похоже на this question). Платформа базы данных - это SQL Server 2005, а каталог довольно большой (750 000 продуктов, 8500 разделов каталога на 4 уровня), но относительно статично (перезагружается один раз в день), и поэтому нас беспокоит только производительность READ.Иерархическая структура структуры данных (вложенные наборы)

Общая структура иерархии каталога: -

  • Уровень 1 Раздел
    • Уровень 2 Раздел
      • Уровень 3 Раздел
        • Уровень 4 Раздел (продукты связанные с здесь)

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

CREATE TABLE CatalogueSection 
(
    SectionID INTEGER, 
    ParentID INTEGER, 
    LeftExtent INTEGER, 
    RightExtent INTEGER 
) 

CREATE TABLE CatalogueProduct 
(
    ProductID INTEGER, 
    SectionID INTEGER 
) 

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

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

CREATE TABLE CatalogueSectionCount 
(
    SectionID INTEGER, 
    CustomerGroupID INTEGER, 
    SubSectionCount INTEGER, 
    ProductCount INTEGER 
) 

Таким образом, на проблемы Производительность очень плохое на верхних уровнях иерархии. Общий запрос, показывающий «лучшие 10» продуктов в выбранном разделе каталога (и всех дочерних разделах), занимает место в пределах 1 минуты для завершения. На более низких участках иерархии он быстрее, но все еще недостаточно.

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

Мне интересно, является ли дизайн принципиально ошибочным или это потому, что у нас такой большой набор данных? У нас есть разумный сервер разработки (3.8 ГГц Xeon, 4 Гб оперативной памяти), но это просто не работает :)

Спасибо за любую помощь

Джеймс

+1

Возможно, было бы полезно показать нам медленный SQL?Мы могли бы обнаружить что-то, что может стать узким местом. – Jonathan 2008-12-10 10:53:09

ответ

6

Используйте закрывающий стол. Если ваша базовая структура является родительским-потомком с идентификаторами полей и ParentID, структура для таблицы закрытия - это идентификатор и десенсинтант. Другими словами, таблица замыкания является таблицей-предком-потомком, где каждый возможный предок связан со всеми потомками. Если вам нужно, вы можете включить поле LevelsBetween. Реализации таблиц закрытия обычно включают в себя записи саморегуляции, то есть идентификатор 1 является предком идентификатора 1 потомка с уровнями между нулями.

Пример: Родитель/Ребенок
ParentID - ID
1 - 2
1 - 3
3 - 4
3 - 5
4 - 6

предок/Потомок
ID - DescendantID - Уровни между
1 - 1 - 0
1 - 2 - 1
1 - 3 - 1
1 - 4 - 2
1 - 6 - 3
2 - 2 - 0
3 - 3 - 0
3 - 4 - 1
3 - 5 - 1
3 - 6 - 2
4 - 4 - 0
4 - 6 - 1
5 - 5 - 0

таблица предназначена для устранения рекурсивных соединений. Вы загружаете нагрузку рекурсивного соединения в цикл ETL, который вы делаете, когда вы загружаете данные один раз в день. Это смещает его от запроса.

Кроме того, он позволяет иерархии на уровне переменных. Вы не застряли на 4.

Наконец, это позволяет использовать продукты слота в нелистовых узлах. Многие каталоги создают «разные» ведра на более высоких уровнях иерархии для создания листового узла для присоединения продуктов. Вам не нужно это делать, поскольку промежуточные узлы включены в закрытие.

Что касается индексации, я бы сделал кластеризованный индекс в ID/DescendantID.

Теперь для выполнения вашего запроса. Это занимает часть, но не все. Вы упомянули «Топ-10». Это подразумевает ранжирование по ряду фактов, о которых вы не упомянули. Нам нужны детали, чтобы помочь их настроить. Кроме того, это получает только участки листового уровня, а не продукты. По крайней мере, у вас должен быть указатель на ваш каталог, который заказывается по разделуID/ProductID. Я бы привязал секцию к продукту как объединение циклов на основе мощности, которую вы предоставили. Отчет о разделе каталога перейдет в таблицу закрытия, чтобы получить потомков (используя поиск кластеризованного индекса). Затем этот список потомков будет использоваться для получения продуктов из CatalogueProduct с использованием индекса по зацикленному индексу. Затем, с этими продуктами, вы получите факты, необходимые для ранжирования.

0

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

0

Возможно ли вычислить ProductCount и SubSectionCount после загрузки каждый день?
Если данные изменяются только один раз в день, то, конечно, стоит вычислить эти цифры, даже если требуется некоторая денормализация.

+0

Да, мы уже предварительно вычислили их ежедневно. Это не столько подсчет продуктов, которые являются проблемой, но и фактический список продуктов в выбранном разделе, который медленный. – James 2008-12-10 15:23:26

+0

Вы обновляете статистику после перезагрузки данных? Если ваши индексы в порядке (настроены для использования только для чтения), может быть, вы возвращаете слишком много данных? Это область, на которую я могу смотреть дальше. TBH, чтобы помочь больше, будет довольно сложно, не видя схемы и/или хранимые процедуры. – Bravax 2008-12-10 15:32:39

Смежные вопросы