Все главы учебника
Содержание учебника
Глава 12 / Базы данных

Реляционные таблицы, документы и выбор по запросам

15 мин чтенияКонтент v0.10.0

Клуб добавляет сведения о книгах: у одной есть переводчик, у другой — ссылка на электронное приложение, у третьей — номер тома. Разработчик предлагает перенести всё в документную базу, чтобы больше не менять таблицы. Но список активных выдач, проверка свободного экземпляра и отчёт по читателям никуда не исчезли. Выбор хранилища начнём с запросов и ограничений, затем проверим, какую часть задачи уже решает PostgreSQL.

Разные способы представления

Реляционная модель хранит отношения с определёнными атрибутами и позволяет соединять их по ключам. Документная модель группирует вложенные данные, часто в JSON-подобных документах. Хранилище ключ–значение отдаёт значение по ключу; графовая модель делает центральными вершины и связи. Эти названия описывают подходы, а не общую гарантию скорости или согласованности. Две документные системы могут иметь разные транзакции, индексы и ограничения.

Документ книги удобно читать целиком: название, авторы, необязательные характеристики. Но если вложить в него все выдачи, появятся растущий документ и конкурентные изменения одного объекта. Если копировать имя читателя в каждую выдачу, нужно решить, это историческое имя на момент выдачи или текущее имя, которое надо обновлять в нескольких местах. Эти вопросы существовали и в нормализации таблиц; другой формат не отменяет смысл данных.

Ключ–значение подходит, когда ключ и способ чтения заранее известны, например временная сессия по случайному идентификатору. Поиск всех просроченных выдач по дате уже требует дополнительной структуры или перебора. Граф полезен, когда задача содержит обходы связей переменной глубины, например цепочки рекомендаций между участниками. Несколько внешних ключей ещё не делают любую библиотеку задачей для графового движка.

JSON внутри PostgreSQL

JSONB позволяет хранить гибкие атрибуты рядом с реляционными ключами. Он не сохраняет исходное форматирование JSON и порядок ключей объекта; одинаковые ключи не подходят для представления отдельных повторяющихся фактов. Структура ниже — отдельный обратимый опыт, а не новая обязательная колонка golden seed.

BEGIN;
CREATE TEMP TABLE book_attributes (
  book_id bigint PRIMARY KEY,
  attrs jsonb NOT NULL CHECK (jsonb_typeof(attrs) = 'object')
);
INSERT INTO book_attributes VALUES
(10, '{"format":"paper","pages":240}'),
(11, '{"format":"ebook","downloadable":true}'),
(12, '{"format":"paper","volume":2}');
SELECT book_id, attrs->>'format' AS format
FROM book_attributes
WHERE attrs @> '{"format":"paper"}'::jsonb
ORDER BY book_id;
ROLLBACK;

Ожидаются 10 и 12 со значением paper. Оператор ->> извлекает текст, а @> проверяет включение JSONB. Временная таблица живёт в текущем соединении; здесь она откатывается вместе с транзакцией. Мы намеренно не добавили внешний ключ из временной таблицы к постоянной books: PostgreSQL не разрешает такую связь. В постоянной схеме соответствующий внешний ключ можно создать между постоянными таблицами.

Отсутствующий атрибут и строка с пустым текстом — разные случаи. SQL NULL, JSON null и отсутствие ключа тоже надо различать. Для проверки наличия ключа объект поддерживает оператор ?, например attrs ? 'pages'. Приведение произвольного текста к integer может закончиться ошибкой; свободная структура требует проверки формы входных данных. Если число страниц обязательно и участвует в регулярных фильтрах, обычная типизированная колонка часто понятнее.

Цена гибкости

Отсутствие общей схемы не означает отсутствие требований. Приложение всё равно ожидает определённые поля и типы. Если старая версия пишет pages числом, а новая строкой, потребители должны понимать оба варианта или данные нужно преобразовать. Такая миграция может стать менее заметной, но не менее необходимой. Документам также нужны правила версий и проверка качества.

PostgreSQL поддерживает индексы для JSONB, включая GIN, а также выражения над отдельными полями. Индексирование увеличивает место и стоимость изменения данных. Для трёх документов план не докажет преимущество индекса: объём слишком мал. Повторите метод восьмого урока на характерном числе строк, распределении атрибутов и реальных фильтрах. Измеряйте чтение и запись, а не только красивый короткий запрос.

Основное правило библиотеки — один активный заём на физический экземпляр — уже выражено частичным уникальным индексом. При переносе в другую систему нужно найти столь же конкретный механизм. Проверка «сначала прочитай, затем запиши» без защиты от конкуренции допускает две одновременные выдачи. Транзакции внутри одного документа не обязательно обеспечивают ограничение между несколькими документами; точные границы проверяются в документации выбранного продукта.

Распределение по нескольким узлам также не даёт бесплатного роста. Нужно выбрать ключ разбиения, учесть горячие ключи, сетевые отказы и запросы, затрагивающие несколько частей. CAP описывает невозможность одновременно обеспечить определённую согласованность и доступность при сетевом разделении, а не таблицу «SQL против NoSQL». Для локальной учебной библиотеки такой отказ не является причиной менять модель.

Таблица решения

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

Реплика поискового каталога может быть производной: её можно пересобрать из основной базы. Тогда устаревший поиск допустим в оговорённых границах, но разрешение выдачи проверяется по источнику истины. Потеря поискового индекса и потеря единственного списка выдач имеют разные последствия. Назовите это различие до выбора инструмента.

Самостоятельная лаборатория

Отведите 35–45 минут. Выполните JSONB-опыт и добавьте внутри той же транзакции четвёртый документ без format. Предскажите результат фильтра по paper и проверки существования ключа. Затем составьте короткую таблицу требований для каталога, выдач и временных сессий. Предложите представление каждой части и назовите механизм защиты её главного инварианта.

Критерии готовности: вы получаете ожидаемые книги, различаете отсутствующий ключ и значение, не считаете слово NoSQL гарантией масштабирования и можете объяснить цену отдельной системы. Не требуется устанавливать второй сервер ради сравнения названий.

Разбор

Гибкие характеристики можно хранить в JSONB, а связи читателей, экземпляров и выдач оставить типизированными. Это решение соответствует известным запросам и проверяемым ограничениям. Если требования поменяются, например появятся глубокие обходы связей или огромный поток короткоживущих значений, выбор пересматривается по измерению и гарантиям конкретной системы. Сравнение должно включать стоимость восстановления и сложность команды, а не только формат данных.

Первичные источники

PostgreSQL: JSON и JSONB, операторы JSON, GIN.