Модели данных: от иерархической до документной

Модель данных — это способ договориться, как описывать факты мира. Разберём четыре исторических подхода и поймём, почему реляционный победил.

Модель данных — это набор понятий для описания структуры данных, операций над ними и ограничений целостности. Это «грамматика», на которой формулируется схема любой базы.

Зачем нужна модель данных

Прежде чем хранить данные, нужно договориться о языке их описания. Можно ли вкладывать одну сущность в другую? Как выражается связь «у заказа много товаров»? Можно ли у записи не указывать поле? Ответы на эти вопросы и задаёт модель данных. Она работает на трёх уровнях: структура (из чего состоят данные), операции (что с ними можно делать) и ограничения (какие состояния считаются допустимыми). Меняя модель, мы меняем сам способ мышления о данных — поэтому история СУБД во многом есть история смены моделей.

Иерархическая модель

Самая ранняя (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, реляционная модель устарела». Нет: это разные инструменты под разные задачи, и реляционные базы остаются основой большинства систем.

Итог

  • Модель данных задаёт структуру, операции и ограничения — это язык описания данных.
  • Иерархическая (дерево) и сетевая (граф) модели страдали от навигационных запросов и зависимости от структуры хранения.
  • Реляционная модель ввела единообразное табличное представление, связи через значения и декларативные запросы — отсюда её доминирование.
  • Документная модель вернула вложенность ради гибкости и масштаба, пожертвовав удобством сложных связей.
Проверьте себя
1. Почему иерархическая модель плохо выражает связь «многие-ко-многим»?
AВ дереве у узла может быть только один родитель
BВ дереве нельзя хранить числа
CДерево не поддерживает индексы
DВ дереве запрещены дубликаты
2. Чем декларативный запрос реляционной модели отличается от навигационного в сетевой?
AОн быстрее по определению
BОн описывает что нужно получить, а не как обойти структуру
CОн работает только с одной таблицей
DОн не использует ключи
3. Как реляционная модель выражает связи между таблицами?
AФизическими указателями между записями
BСовпадением значений: внешний ключ ссылается на ключ другой таблицы
CВложением одной таблицы в другую
DЧерез отдельный файл связей
Поддержать проект