Модели данных: от иерархической до документной
Модель данных — это способ договориться, как описывать факты мира. Разберём четыре исторических подхода и поймём, почему реляционный победил.
Модель данных — это набор понятий для описания структуры данных, операций над ними и ограничений целостности. Это «грамматика», на которой формулируется схема любой базы.
Зачем нужна модель данных
Прежде чем хранить данные, нужно договориться о языке их описания. Можно ли вкладывать одну сущность в другую? Как выражается связь «у заказа много товаров»? Можно ли у записи не указывать поле? Ответы на эти вопросы и задаёт модель данных. Она работает на трёх уровнях: структура (из чего состоят данные), операции (что с ними можно делать) и ограничения (какие состояния считаются допустимыми). Меняя модель, мы меняем сам способ мышления о данных — поэтому история СУБД во многом есть история смены моделей.
Иерархическая модель
Самая ранняя (IBM IMS, конец 1960-х). Данные организованы в виде дерева: у каждой записи ровно один «родитель», связи идут сверху вниз. Это естественно для строго древовидных предметных областей — например, оглавление книги или организационная структура.
Минусы видны сразу на примере магазина. Связь «многие-ко-многим» дерево выразить не умеет: один заказ содержит много товаров, и один товар входит во многие заказы — а у узла дерева только один родитель. Приходится дублировать данные о товаре в каждом заказе, и мы возвращаемся к избыточности. Навигация жёсткая: чтобы добраться до данных, программа спускается по дереву от корня, зная его форму, — это снова зависимость данных.
Сетевая модель
Сетевая модель (CODASYL, 1970-е) сняла главное ограничение дерева: у записи может быть несколько родителей, связи образуют граф. Теперь «многие-ко-многим» выразимо. Но цена — сложность. Программист вручную прокладывал «наборы» (sets) указателей между записями и перемещался по ним операторами вроде «найти следующего владельца этого набора». Запросы превратились в навигационный код: чтобы получить данные, нужно было пошагово обойти граф. Логика хранения снова просвечивала сквозь приложение.
Парадокс сетевой модели в том, что она была технически мощнее иерархической, но проиграла именно из-за сложности использования. Чтобы написать запрос, разработчик должен был держать в голове всю топологию связей и аккуратно вести курсоры по наборам. Малейшая ошибка в порядке обхода давала неверный результат. Поддерживать такой код было тяжело, а переносить между системами — почти невозможно. Этот опыт и подсказал главную идею реляционной модели: спрятать навигацию внутрь СУБД и дать пользователю декларативный язык. Сложность никуда не делась — её просто переложили с программиста на оптимизатор.
Реляционная модель
В 1970 году Эдгар Кодд предложил радикально иной подход: представить все данные единообразно — в виде отношений (таблиц). Никаких указателей и навигации: связи выражаются совпадением значений (внешний ключ ссылается на ключ другой таблицы). Запросы формулируются декларативно — на языке множеств и логики (реляционная алгебра, а на практике SQL): мы говорим, что хотим получить, а не как это извлечь. СУБД сама выбирает способ выполнения. Это дало физическую независимость данных и сделало реляционные базы доминирующими на десятилетия.
Принципиально, что реляционная модель опирается на математику — теорию множеств и логику предикатов. Это не «таблички для удобства», а строгий формализм, в котором поведение запросов можно доказывать, а не угадывать. Из этого формализма растёт вся теория, которой посвящён курс: реляционная алгебра, функциональные зависимости, нормализация. Единообразие («всё есть отношение») — ещё одна сила модели: и данные, и результаты запросов, и промежуточные шаги — всё это отношения, поэтому операции свободно комбинируются. Этой комбинируемости мы посвятим целый раздел.
CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE order_items (order_id INTEGER, product_id INTEGER, qty INTEGER);
INSERT INTO products VALUES (1, 'Книга'), (2, 'Кружка');
INSERT INTO order_items VALUES (100, 1, 2), (100, 2, 1), (101, 1, 3);
-- связь "многие-ко-многим" выражается совпадением значений, без указателей
SELECT p.name, SUM(oi.qty) AS prodano
FROM order_items oi
JOIN products p ON p.id = oi.product_id
GROUP BY p.name;
Почему навигация — это плохо
Стоит задержаться на ключевом недостатке иерархической и сетевой моделей, потому что именно его исправила реляционная. В этих моделях запрос — это процедура обхода: «возьми корень, спустись к первому потомку, перейди к следующему по набору». Программист фактически пишет план выполнения вручную. Из этого вытекают три беды. Во-первых, запрос привязан к физической структуре: изменилась раскладка — переписывай код. Во-вторых, оптимизировать выполнение некому: СУБД лишь исполняет заданные шаги, она не вольна выбрать лучший порядок. В-третьих, такой код сложен и хрупок. Реляционная модель разорвала эту связь: запрос описывает результат, а как его получить — забота оптимизатора. Эту мысль мы разовьём в разделе про реляционную алгебру, где увидим, во что именно компилируется декларативный SQL.
Документная модель
Документная модель (MongoDB, CouchDB) — современный представитель семейства NoSQL. Единица хранения — документ, обычно в формате JSON, который может содержать вложенные объекты и массивы. По сути это управляемое «дерево» внутри одной записи. Заказ можно хранить целиком: внутри него массив товаров с их количествами и даже копией нужных полей товара.
Вот как мог бы выглядеть заказ как документ. Обратите внимание: данные клиента и позиции вложены прямо внутрь — читать такой объект удобно одним обращением.
{
"order_id": 100,
"client": { "name": "Анна", "city": "Москва" },
"items": [
{ "product": "Книга", "qty": 2 },
{ "product": "Кружка", "qty": 1 }
]
}
Плюс очевиден: чтобы показать заказ, не нужно соединять несколько таблиц — всё рядом. Минус проявляется, когда нам нужно, например, переименовать товар: его название скопировано во множество документов-заказов, и согласованность копий ложится на приложение. Это та же избыточность, от которой ушла реляционная модель, — здесь она допускается сознательно ради удобства и скорости чтения. Подробно компромиссы NoSQL мы разберём в последнем разделе курса.
Сильная сторона — гибкость схемы и удобство, когда данные естественно читаются и пишутся целым «куском» (профиль пользователя, карточка заказа). Слабая — та же, что у иерархии: связи «многие-ко-многим» и сложные соединения по нескольким сущностям выражаются хуже, появляется контролируемая избыточность, а согласованность дублей ложится на приложение. Подробно про NoSQL мы поговорим в последнем разделе.
Три составляющие любой модели данных
Чтобы сравнивать модели предметно, полезно смотреть на три их составляющие, о которых мы упоминали. Структурная часть отвечает на вопрос «из чего состоят данные»: дерево, граф, таблицы, документы. Манипуляционная часть — «что с данными можно делать»: навигация по указателям или декларативные запросы. Целостностная часть — «какие состояния допустимы»: какие ограничения модель умеет выражать. Реляционная модель сильна во всех трёх: ясная структура (отношения), мощная манипуляция (реляционная алгебра и SQL) и богатый аппарат ограничений (ключи, ссылочная целостность). Именно эта полнота, а не только «таблички», объясняет её успех.
Заметьте закономерность: чем ближе модель к тому, как данные лежат физически (иерархия, сеть с указателями), тем больше запрос превращается в навигационный код. Чем абстрактнее модель (реляционная), тем декларативнее запрос — и тем больше свободы у СУБД оптимизировать выполнение. Это прямое следствие независимости данных, которой мы посвятим отдельный урок.
Реляционная модель ближе: ещё пример связей
Покажем на SQL, как одна и та же сущность участвует в разных связях без всякой навигации. У клиента много заказов (1:N), а заказ содержит много товаров (M:N) — и всё это собирается декларативно.
CREATE TABLE clients (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE orders (id INTEGER PRIMARY KEY, client_id INTEGER);
INSERT INTO clients VALUES (1,'Анна'),(2,'Борис');
INSERT INTO orders VALUES (10,1),(11,1),(12,2);
-- сколько заказов у каждого клиента — один декларативный запрос
SELECT c.name, COUNT(o.id) AS zakazov
FROM clients c LEFT JOIN orders o ON o.client_id = c.id
GROUP BY c.name
ORDER BY c.name;
Вывод: «Анна — 2», «Борис — 1». Мы не писали обхода указателей — лишь описали желаемый результат. В сетевой модели тот же результат потребовал бы вручную пройти набор заказов каждого клиента.
Сравнение
| Модель | Структура | Связь M:N | Запросы |
| Иерархическая | Дерево | Плохо (дублирование) | Навигация по дереву |
| Сетевая | Граф указателей | Да, но вручную | Навигация по наборам |
| Реляционная | Таблицы (отношения) | Через связующую таблицу | Декларативно (SQL) |
| Документная | Вложенные документы (JSON) | Хуже, частая денормализация | По документу / агрегации |
Типичные заблуждения
- «Документные базы — это что-то принципиально новое». По духу они близки к иерархической модели полувековой давности; новизна — в масштабируемости и гибкой схеме, а не в самой идее вложенности.
- «Реляционная модель — это про таблицы как в Excel». Таблица — лишь внешняя форма; суть в строгой математической основе (отношения, множества) и декларативных запросах.
- «Раз есть NoSQL, реляционная модель устарела». Нет: это разные инструменты под разные задачи, и реляционные базы остаются основой большинства систем.
Итог
- Модель данных задаёт структуру, операции и ограничения — это язык описания данных.
- Иерархическая (дерево) и сетевая (граф) модели страдали от навигационных запросов и зависимости от структуры хранения.
- Реляционная модель ввела единообразное табличное представление, связи через значения и декларативные запросы — отсюда её доминирование.
- Документная модель вернула вложенность ради гибкости и масштаба, пожертвовав удобством сложных связей.