32
Построение индекса по иерархии записей в реляционной БД Андрей Майоров. BYTE-force

Построение индекса по иерархии записей в реляционной БД

Embed Size (px)

DESCRIPTION

Доклад на SEF-2009

Citation preview

Page 1: Построение индекса по иерархии записей в реляционной БД

Построение индекса по иерархии записей в реляционной БД

Андрей Майоров. BYTE-force

Page 2: Построение индекса по иерархии записей в реляционной БД

Дано

• Реляционная база данных.• Данные логически образуют

иерархическую структуру. • Каждая запись может иметь более

одного предка, но в иерархии не должно быть циклов.

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

Object *id

name

Relation *parent

child

Page 3: Построение индекса по иерархии записей в реляционной БД

Задача

• Максимально быстро выбирать всех потомков или всех предков заданной записи или группы записей.

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

• Быстро добавлять и удалять записи из иерархии.

Page 4: Построение индекса по иерархии записей в реляционной БД

Зачем?

• Выбирать сотрудников из отдела.• Товары из раздела каталога.• Статьи из раздела с подразделами.• ...

Page 5: Построение индекса по иерархии записей в реляционной БД

Стандартные средства

Pho

to b

y ht

tp://

ww

w.fl

ickr

.com

/pho

tos/

shan

nonp

atric

k17/

Page 6: Построение индекса по иерархии записей в реляционной БД

employeeemployee_id

name

manager

CONNECT BY в Oracle

• Выбирает работника по имени John, его подчиненных, подчиненных его подчиненных, etc.

• Псевдоколонка LEVEL позволяет ограничить удаленность от стартовой записи.

SELECT name FROM employeeSTART WITH name = 'John'CONNECT BY PRIOR employee_id = manager

Page 7: Построение индекса по иерархии записей в реляционной БД

Недостатки CONNECT BY

• Выполняется столько запросов, сколько существует уровней иерархии.

• Довольно сложно делать иерархии, содержащие записи из разных таблиц.

• Подход работает только в Oracle.

Page 8: Построение индекса по иерархии записей в реляционной БД

Common Table Expressions

• Первый SELECT внутри CTE заменяет START WITH, • второй – CONNECT BY PRIOR.

WITH t( employee, name ) AS (SELECT employee_id, name FROM employeeWHERE name = 'John'UNION ALLSELECT next.employee_id, next.name FROM employee as next

INNER JOIN t ON t.employee_id = next.manager )SELECT name FROM t

Page 9: Построение индекса по иерархии записей в реляционной БД

Сравним CTE с CONNECT BY

• Скорость работы должна быть сравнимой.• Гетерогенные иерархии строятся проще. При помощи

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

• CTE поддерживаются основными коммерческими СУБД, но не популярными СУБД с открытым кодом (MySQL, Firebird, PostgreSQL и др.).

Page 10: Построение индекса по иерархии записей в реляционной БД

Построение иерархии вручную

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

• Такой подход похож на CTE. Может быть реализован в любой базе данных.

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

Page 11: Построение индекса по иерархии записей в реляционной БД

Стандартные средства...

Хороши

• Не требуют дополнительных таблиц

• Очень просто вставлять и удалять записи

Плохи

• Сопряжены с итеративной выборкой связанных записей.

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

Page 12: Построение индекса по иерархии записей в реляционной БД

Как ускорить выборку?

• Решение – самостоятельно сформировать «индекс» во вспомогательном поле или таблице.

• Варианты хранения индекса:

– Lineage (materialized path)– Левые и правые индексы– Карта связей

Page 13: Построение индекса по иерархии записей в реляционной БД

Lineage (линидж)

Ист

очни

к -

htt

p:/

/ww

w.s

qlte

am

.co

m/a

rtic

le/m

ore

-tre

es-

hie

rarc

hie

s-in

-sq

l

EmployeeID Name BossID

1001 Denis Eaton-Hogg NULL

1002 Bobbi Flekman 1001

1003 Ian Faith 1002

1004 David St. Hubbins 1003

1005 Nigel Tufnel 1003

1006 Derek Smalls 1003

Denis Eaton-Hogg Bobbi Flekman Ian Faith David St. Hubbins Nigel Tufnel Derek Smalls

Node ParentNode EmployeeID Depth Lineage

100 NULL 1001 0 /

101 100 1002 1 /100/

102 101 1003 2 /100/101/

103 102 1004 3 /100/101/102/

104 102 1005 3 /100/101/102/

105 102 1006 3 /100/101/102/

Индекс

Данные

Page 14: Построение индекса по иерархии записей в реляционной БД

Lineage

За

• Все просто и понятно.• Легко добавлять детей к

любому узлу.

Против

• Достаточно сложно «перевесить» узел – надо пересчитывать пути для всей ветки.

• Годится только для деревьев.

• Выборки сопряжены со строковыми операциями.

Page 15: Построение индекса по иерархии записей в реляционной БД

Левые и правые индексы

Ист

очни

к -

htt

p:/

/ww

w.o

sp.r

u/o

s/2

00

3/0

4/1

82

94

2/

ID Name ParentID

1 Предприятие NULL

2 Управление 1

3 Инфраструктура 1

4 Производство 1

5 Энергия 3

6 Сервисные услуги 3

7 Месторождение А 4

8 Месторождение Б 4

1 Предприятие 2 Управление 3 Инфраструктура 5 Энергия 6 Сервисные услуги 4 Производство 7 Месторождение А 8 Месторождение Б

ID Left Right Level IsLeaf

1 1 16 1 N

2 2 3 2 Y

3 4 9 2 N

4 10 15 2 N

5 5 6 3 Y

6 7 8 3 Y

7 11 12 3 Y

8 13 14 3 Y

Индекс

Данные

1

2 3 4

5 6 7 8

1

2 3 4 9

5 6 7 8

10

16

15

11 12 13 14

Page 16: Построение индекса по иерархии записей в реляционной БД

Левые и правые индексы

За

• Выборки используют сравнения целых чисел.

Против

• Очень сложно вставить узел в середину.

• Годится только для деревьев.

Page 17: Построение индекса по иерархии записей в реляционной БД

Карта связей

Image by http://www.flickr.com/photos/36041246@N00/3344881664/

Page 18: Построение индекса по иерархии записей в реляционной БД

Карта связей

RelationMapparent

child

distance

count

Objectid

name

Relationparent

child

Page 19: Построение индекса по иерархии записей в реляционной БД

Карта связей

За

• Несколько родителей у узла.• Выборка проводится

«просто по индексу».• Данные можно хранить в

covering index.• Позволяет делать

гетерогенные иерархии• Есть методика

эффективного обновления индекса.

Против

• При большой вложенности индексная таблица может быть очень большой.

RelationMapparent

child

distance

count

Page 20: Построение индекса по иерархии записей в реляционной БД

Рассмотрим граф

• Выше – родители, ниже – дети.

• Пути считаем идущими снизу вверх.

• Длина пути равна количеству ребер.

• Есть пути с одинаковой длиной.

1

2 3

4 5

76

Page 21: Построение индекса по иерархии записей в реляционной БД

Новая связь между 7 и 8

Появляются новые пути:• Прямой путь из 8 в 7 (длина 1).• Пути из 8 через 7 во все объекты, в

которые можно попасть из 7.

Длина всех путей будет на единицу больше, чем из 7.

1

3

4 5

7

8

A

Page 22: Построение индекса по иерархии записей в реляционной БД

Новая связь между 7 и 8

• Прямой путь из 8 в 7 (длина 1).• Из 8 ко всем предкам объекта 7

(длина + 1).• Из 7 ко всем потомкам объекта 8

(длина + 1).• Отовсюду, докуда есть пути из 7,

мы теперь можем попасть в 9 и A, и наоборот.

Длина пути между 9 и 3 будет равна сумме длин путей до 8 и от 7 соответственно плюс 1.

1

3

4 5

7

8

9 A

B

Page 23: Построение индекса по иерархии записей в реляционной БД

Количество одинаковых путей

• Если из объекта 7 в объект x можно попасть cx путями с длиной dx,

• а из объекта y в объект 8 – cy путями с длиной dy,

• то из y в x можно будет попасть cx*cy путями с длиной dx+dy+1.

1

3

4 5

7

8

9 A

B x

y

Путей: 1 * 2 = 2Длина: 1 + 2 +1 = 4

Page 24: Построение индекса по иерархии записей в реляционной БД

Добавляем связь parent - child

Появляется следующий набор путей:

foreachx in descendants( child ), dx in distances( x, child ), y in ancestors( parent ), dy in distances( parent, y )

добавляется count(x,child,dx)*count(parent,y,dy) путей между x и y с длиной dx+dy+1.

• count(x,y,d) – количество различных путей из x в y с длиной d

• ancestors(x) и descendants(x) содержат x• distances(x, x) == [ ]

Page 25: Построение индекса по иерархии записей в реляционной БД

В нашей схеме данных …

RelationMapparent

child

distance

count

Objectid

name

Relationparent

child

… RelationMap содержит все связи между всеми объектами на данный момент времени.

Значит ее можно использовать для построения множества новых путей.

Page 26: Построение индекса по иерархии записей в реляционной БД

SQL: множество новых путейSELECT d.child, a.parent, a.distance + d.distance + 1,

sum( a.count * d.count )FROM (

SELECT *FROM RelationMapWHERE child = @iParentUNIONSELECT @iParent, @iParent, 0, 1

) AS a, (

SELECT *FROM RelationMapWHERE parent = @iChildUNIONSELECT @iChild, @iChild, 0, 1

) AS dGROUP BY d.object, a.ancestor, a.distance + d.distance + 1

Page 27: Построение индекса по иерархии записей в реляционной БД

Принципы работы запроса

• Выбираются все пути, в которых @iParent – потомок. К выборке добавляется путь от этого объекта к самому себе с длиной 0 и количеством повторений 1.

• Выбираются все пути, в которых @iChild – предок. Также добавляется путь к самому себе.

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

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

Page 28: Построение индекса по иерархии записей в реляционной БД

SQL: добавление путей

UPDATE RelationMapSET count = om.count + st.countFROM @tSubTree stJOIN RelationMap omON st.child = om.childAND st.parent = om.parentAND st.distance = om.distance INSERT INTO RelationMapSELECT st.child, st.parent, st.distance, st.countFROM @tSubTree stLEFT JOIN RelationMap omON st.child = om.childAND st.parent = om.parentAND st.distance = om.distanceWHERE om.child IS NULL

Page 29: Построение индекса по иерархии записей в реляционной БД

SQL: удаление путей

UPDATE RelationMapSET count = om.count - st.countFROM @tSubTree stJOIN RelationMap omON st.child = om.childAND st.parent = om.parentAND st.distance = om.distance DELETEFROM RelationMapWHERE count = 0

Page 30: Построение индекса по иерархии записей в реляционной БД

Особенности алгоритма

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

• Карта путей помогает избежать появления циклов в графе объектов. Для этого достаточно проверить, не существует ли уже путей, ведущих от @iParent к @iChild (т.е. в обратном направлении).

• Все это очень удобно делать в триггерах на таблице связей.

Page 31: Построение индекса по иерархии записей в реляционной БД

Гетерогенный случай

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

• Основной сложностью в этом случае является построение такой таблицы RelationMap, которая могла бы хранить ссылки на объекты разных типов.

• Обобщающую таблицу с путями, можно поддерживать тем же способом – при помощи триггеров на таблицах со связями.

Page 32: Построение индекса по иерархии записей в реляционной БД

В заключение

• Придумали сами.• Используем уже несколько лет.• И вам советуем.

• Подробная статья – на сайте: http://blogs.byte-force.com/files/folders/articles_ru/entry1148.aspx