Эволюционирующая схема БД
Post on 07-Dec-2014
702 views
DESCRIPTION
Как сделать схему базы данных приложения более универсальной? Что можно сделать на уровне схемы для того, чтобы сделать ее более адаптируемой к изменениям ТЗ по мере развития проекта? Какие стереотипы при проектировании схемы мешают достижению этих задач?TRANSCRIPT
Эволюционирующая схема БДSustainable Database Schema
Interlabs
14 февраля 2014
1 / 28
Что мы хотим
• единая базовая схема для различных проектов• компонуемая из отдельных функциональных блоков• адаптируемая к особенностям проекта• эволюционирующая вместе с проектом• эволюционно масштабируемая и оптимизируемая• самодостаточная, не требующая для работыдополнительных клиентских средств
Sustainable Database Schema
2 / 28
Сферический проект в вакууме• набор различных типов сущностей с собственныматрибутным составом: продукты, страницы, новости, теги ит.д.
• семантические связи между сущностями• в том числе — таксономия: группы товаров, теговаяклассификация и т.д.
• часто — произвольный набор свойств (E.A.V.) длянекоторых типов: свойства продуктов, товарныхпредложений и т.д.
• различные типовые сервисы: комментарии, рейтинги,контент и т.д.
Сущности + связи + таксономия +наборы свойств + общие сервисы
3 / 28
Тривиальная схемаКаждая сущность в отдельной таблице с AUTO_INCREMENT.
Сущности изолированы внутри таблиц за счетлокальности идентификатора.
4 / 28
Проблемы тривиальной схемы• сложность реализации общих универсальных сервисов:комментирование и рейтингование сущностей, поиск посайту и т.д.
• сложность построения связей между разнороднымисущностями: с этим товаром покупают, новости о товаре,читай также и т.д.
• EAV-поля для разнородного контента (если нужен EAV).
Главная проблема: своя последовательность idдля каждого типа
• нельзя однозначно идентифицировать сущность• нельзя сослаться на произвольную сущность
5 / 28
Глобальный идентификатор• единая последовательность для всех сущностей;• глобальность id определяется алгоритмом генерации• ссылочная целостность — общая таблица сущностей
6 / 28
Таблица сущностей
• заполняется автоматически при вставке в частные таблицы• type определяет тип сущности, можно использовать ENUM;• внешние ключи с частных таблиц — каскадное удалениепри удалении из общей таблицы;
• дополнительные триггеры на удаление из частных таблиц;• если необходимо — дополнительные поля,характеризующие сущность в целом (даты создания иизменения, права доступа и т.д.)
Если таблица ведется полностью автоматически, можноиспользовать даже на legacy-проектах.
7 / 28
Общая структура
Общий аспект сущностей = отдельная таблица.
8 / 28
Общая структура
• наличие единой последовательности id позволяет легкорасширять функциональность сущностей;
• у разных видов сущностей могут быть общие аспекты:отображаемый ресурс, результат поиска, категория и т.д.);
• факт наличия сущности в таблице определяет наличие унее соответствующего аспекта;
• если необходимо, таблица аспекта может содержать поле стипом сущности для упрощения обработки;
• общие сервисы используют общую таблицу сущностей дляссылочной целостности.
9 / 28
Генерация общего idAUTO_INCREMENT в entity
• уникален в пределах набора таблиц, последователен• клиент без изменений кроме непосредственно вставки• наиболее подходящий вариант для legacy проектов
UUID_SHORT()
• глобально уникален, последователен• теоретически подходит для миграции данных• можно использовать вообще для всех таблиц• необходима поддержка на уровне клиентского кода• помним о размерности int на клиенте
10 / 28
AUTO_INCREMENT в entity• перед вставкой в рабочую таблицу — вставка в entity• используем полученный id в качестве первичного ключадля рабочей таблицы
Как получить id на клиенте?
LAST_INSERT_ID больше не подходит, поэтому либо:
• два INSERT со стороны клиента, первый возвращает id• хранимая процедура на сервере, выполняет два INSERT,возвращает id в качестве результирующей выборки.
11 / 28
UUID_SHORT()Триггер BEFORE INSERT в рабочей таблице:
IF NOT ‘NEW‘.‘id‘ THENSET ‘NEW‘.‘id‘ = UUID_SHORT();INSERT INTO ‘entity‘(‘id‘, ‘type‘) VALUES( ‘NEW‘.‘id‘, ’PRODUCT’);
END IF;
• на клиенте явно получаем UUID_SHORT() и используем привставке в рабочую таблицу;
• при работе с базой напрямую просто делаем вставку,entity заполняется автоматически.
12 / 28
Связи между сущностямиЧем более семантически связен контент, тем больше связеймежду различными сущностями:
товар принадлежит категории, с этим товаром покупают, другие товарыбренда, аналоги товара, новости про этот товар и т.д.
• максимум возможных связей при минимальной схеме• ссылочная целостность и каскадное удаление дляупрощения клиента
Нужно описывать связи на общем уровне.
13 / 28
Таблица связей
14 / 28
Таблица связей
• type определяет вид связи (проще всего — ENUM)• sourceType и targetType дублируют типы сущностей• внешний ключ по id и типу — ссылочная целостность• поля типов могут быть использованы для контролядопустимости отношения триггером
• каскадное удаление отношений при удалении сущностей• наличие таблицы связей не означает, что все cвязи нужноустанавливать через эту таблицу
• однако автоматическое дублирование связей вида 1→Nможет быть полезно, если по мере развития проекта онипревращаются в M→N.
15 / 28
Связи 1→ NНаличие таблицы связей не означает, что все связи через нее:
• постановка предусматривает 1→ N между сущностями —работаем с ссылочным полем
• есть шанс, что со временем отношение станет N→ M —автоматически дублируем отношение в таблице связей
Результат: ничего не теряем с точки зрения сложности клиента,получаем страховку на случай смены ТЗ, иногда выгоднотрактовать ссылку как общее отношение.
ДА товар→ группа товаров.НЕТ товарное предложение→ товар.
16 / 28
ТаксономияСущности классифицируются другими сущностями, например:
• группы товаров• производители товаров• теги (для всех видов сущностей) и т.д.
Много различных вариантов категоризации по мере развитияпроекта, поэтому реализуем на общем уровне:
• категория — вид сущности (таблицы entity + category)• древовидность в таблице category• связь N→ M через relations• категория — аспект сущности• принадлежность категории — отношение
17 / 28
Таксономия
18 / 28
Entity Attribute Value
• для данных фиксированной структуры всегда используемобычные таблицы, часто выгодно вынести отдельныйаспект данных в отдельную таблицу
• используем EAV когда это действительно нужно (например,каталог товаров)
• не привязываем EAV к конкретному виду сущности —никогда не знаешь, что будет потом
• эффективный поиск — с использованием Sphinx
EAV — общий сервис для всех сущностей
19 / 28
Универсальный EAV
20 / 28
EAV: атрибуты
NUM число, STR строка, DAT дата, REF справочник
• четырех типов достаточно, например, для импортаCommerceML
• для каждого типа — свое поле для хранения значения• другие поля — для денормализации, если это имеет смысл• атрибуты нужно группировать в наборы для облегченияадминистрирования
• набор атрибутов — категория, отношение «категория→атрибут» — 1→ N или N→ M
21 / 28
EAV: ссылочные атрибуты
Наличие значения ссылочного атрибута =классификация по этому значению
• значение ссылочного атрибута — сущность• значение ссылочного атрибута — категория, привязанная катрибуту (дополнительное поле в category)
• category — справочник атрибутов• установка ссылочного атрибута дублируется в relation
Две точки зрения на ссылочный атрибут: значение свойства(attribute_value) и классификация (relation).
22 / 28
Фасетный поискТаксономия: индекс Sphinx c MVA-атрибутом по таблице
relationsАтрибуты: индекс Sphinx с JSON-атрибутами по таблице
attribute_value
• для ссылочных атрибутов можно обойтись одниминдексом по relation
• числовые атрибуты может быть выгодно преобразовать вссылочные, разбив значения на диапазоны.
Единая трактовка таксономии и ссылочных атрибутов можетупростить поиск.
23 / 28
Счетчики и каскадКаскадное удаление существенно упрощает клиент, но нужноиспользовать осмотрительно, избыточный cascade можетвычистить половину базы:
delete="cascade"
Для удаления элементовсущности: значения атрибутовсущности, отношениясущности.
delete="restrict"
Для запрета удалениясвязанных сущностей: группыили бренда при наличиитоваров и т.д.
Проблема: без дополнительных запросов определить наличиеrestrict на клиенте, для блокировки действия.
24 / 28
Поле счетчика
• должно заполняться автоматически триггерами• для 1→ N есть стандартное расширение в baser:
<Table name="comment_reply" comment="Комментарии"><Column name="thread" type="&id;" comment="Тред"/><ForeignKey name="thread" table="thread"
comment="ссылка на тред"ext:count="numOfReplies" <- триггеры генерируются автоматически> 25 / 28
API схемыБаза должна быть самодостаточна, поэтому:
• типовые операции, не укладывающиеся в одноSQL-выражение, выносим в хранимые процедуры;
• периодическое обслуживание базы (архивирование,удаление неактуальных данных и т.д.) выносим в events;
• весь хранимый код формирует API схемы, в будущем —документируемый средставами baser;
• API — не только для приложения, но и для человека,работающего с базой руками.
Иногда реализация хранимой — способ избежать большихизменений на legacy-клиентах (пример с хранимой длясоздания сущности).
26 / 28
Использование baser• проще писать большое количество хранимого кода• часть хранимого кода и структуры генерируетсяавтоматически расширениями
• можно (и нужно!) унифицировать определения типовданных внутри схемы:
<!DOCTYPE Schema [<!ENTITY entities "ENUM(’PRODUCT’, ’CATEGORY’, ’FAQ’, ’FILE’, ’NEWS’)"><!ENTITY attributes "ENUM(’NUM’, ’STR’, ’DAT’, ’REF’)"><!ENTITY id "MEDIUMINT UNSIGNED"><!ENTITY name "VARCHAR(255)">
]>...
<Column name="type" type="&entities;" ... /><Column name="name" type="&name;" .../>
...
27 / 28
Итого
• небольшой базовый набор концепций, расширяющийтрадиционную схему, повышает ее приспособляемость кизменениями ТЗ;
• AUTO_INCREMENT — удобный антипаттерн;• важно мыслить не сущностями в целом, а суммой ихотдельных аспектов;
• даже для legacy-проектов можно извлечь выгоду, вчастности, при организации поиска;
• элементы CMS могут быть реализованы непосредственно вбазе без, собственно, CMS.
28 / 28