Получить доступ

Моделирование данных и целостность

Ключи и constraints, нормальные формы простыми словами, осознанная денормализация и каскадные действия.

Ключи и ограничения как часть модели

Хорошая модель кодирует правила данных в схеме, а не только в коде. Primary key идентифицирует строку, foreign key защищает ссылочную целостность, unique выражает бизнес-уникальность, NOT NULL и CHECK ограничивают допустимые значения.

Эти ограничения — не бюрократия, а машинно-проверяемая документация инвариантов. Схему читают новые разработчики, аналитики и скрипты миграции; правило, записанное в CHECK, невозможно «не заметить», в отличие от проверки, спрятанной в одном из сервисов.

Отдельный сюжет — суррогатный ключ против естественного. Суррогатный (auto id, UUID) стабилен и ничего не значит для бизнеса, поэтому его безопасно менять местами и ссылаться на него. Естественный (email, ИНН, номер паспорта) экономит соединение, но имеет свойство меняться — а меняющийся PK тянет за собой обновление всех ссылающихся строк. Практика по умолчанию: суррогатный PK плюс unique-ограничение на естественный ключ — так получаем и стабильность, и защиту бизнес-уникальности.

create table orders (
  id          bigserial primary key,              -- суррогатный ключ
  tenant_id   bigint not null references tenants(id),
  order_no    text   not null,
  status      text   not null,
  total       numeric(12,2) not null,
  created_at  timestamptz   not null default now(),
  constraint uq_order_no unique (tenant_id, order_no),   -- бизнес-уникальность
  constraint ck_total_non_negative check (total >= 0),   -- инвариант в схеме
  constraint ck_status check (status in ('NEW','PAID','SHIPPED','CANCELLED'))
);

-- колонку-источник FK индексируем сами: автоматически это не делается
create index idx_orders_tenant on orders (tenant_id);

Нормальные формы простыми словами

1NF — атомарные значения, без списков в одной ячейке. 2NF — нет зависимости части атрибутов только от части составного ключа. 3NF — неключевые атрибуты зависят только от ключа, а не друг от друга (нет транзитивных зависимостей).

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

Осознанная денормализация

Денормализация — сознательное дублирование данных ради скорости чтения (меньше join, готовые агрегаты). Это компромисс: чтение дешевеет, но запись усложняется, и появляется риск рассинхронизации.

Денормализуйте после измерений и с явным механизмом поддержания согласованности (триггеры, пересчёт, события), а не «на всякий случай».

Каскадные действия

ON DELETE CASCADE удобен для строк, не имеющих смысла без родителя (позиции заказа), но опасен на больших графах: одно удаление может снести многое. RESTRICT/NO ACTION заставляет приложение явно решить судьбу зависимостей. SET NULL подходит для опциональной связи.

Выбор действия — часть модели, а не деталь реализации.

JSON-колонки и гибкая схема

Современные реляционные СУБД умеют хранить документ прямо в колонке (jsonb в PostgreSQL, JSON в MySQL) и индексировать его содержимое. Это снимает вечный соблазн изобрести EAV — таблицу вида (entity_id, attribute, value), которая формально универсальна, но убивает и запросы, и типизацию, и ограничения.

Разумная граница проходит по предсказуемости: то, по чему фильтруют, соединяют и считают агрегаты, должно быть обычными колонками с типами и ограничениями. То, что действительно произвольно и меняется от записи к записи (атрибуты товара в маркетплейсе, сырой ответ внешнего API, настройки), уместно в jsonb.

Цена гибкости честная: в JSON нет NOT NULL, FK и CHECK для внутренних полей, опечатка в имени ключа не будет замечена базой, а индексы по содержимому дороже обычных. Поэтому «сложим всё в jsonb и разберёмся потом» — это не гибкость, а отложенный технический долг.

create table products (
  id         bigserial primary key,
  sku        text not null unique,        -- предсказуемое → колонка
  price      numeric(12,2) not null,      -- по нему фильтруют → колонка
  attributes jsonb not null default '{}'  -- произвольное → документ
);

-- индекс по содержимому документа
create index idx_products_attrs on products using gin (attributes);
select * from products where attributes @> '{"color": "red"}';

Практический критерий

Если нарушение правила критично для бизнеса, не держите его только в коде — добавьте constraint в БД. Код можно обойти миграцией, багом или вторым сервисом; constraint остаётся последней линией защиты.

Обратная сторона критерия: ограничение в схеме тем полезнее, чем стабильнее правило. Инварианты вроде «сумма не отрицательна» или «номер заказа уникален в рамках тенанта» живут годами. Изменчивую бизнес-логику («скидка не больше 30% по вторникам») в CHECK загонять не стоит — каждое изменение правила превратится в миграцию.

Как отвечать на собеседовании

Нормальные формы объясняйте смыслом, а не заученными формулировками: цель нормализации — хранить каждый факт в ОДНОМ месте, чтобы не было аномалий вставки, обновления и удаления. Дальше уже можно назвать 1NF (атомарные значения), 2NF (нет зависимости от части составного ключа) и 3NF (неключевые атрибуты не зависят друг от друга).

Ключевая фраза, которую ждут: нормализуйте по умолчанию, денормализуйте осознанно — после измерений и с явным механизмом поддержания согласованности (триггер, пересчёт, событие). Денормализация — это обмен: чтение дешевеет, запись усложняется, появляется риск рассинхронизации.

Про ключи дайте готовую позицию: суррогатный PK для стабильности плюс unique на естественный ключ для бизнес-правила. Это лучше, чем абстрактно рассуждать, что «у обоих есть плюсы».

Каскады подавайте как проектное решение: CASCADE для строк, не имеющих смысла без родителя (позиции заказа), RESTRICT когда удаление должно быть осознанным, SET NULL для опциональной связи. И упомяните риск CASCADE на большом графе — одно удаление способно снести многое.

Завершите критерием про constraints: критичные инварианты закрепляем в схеме, потому что это последняя линия защиты, которую не обойдёт ни баг, ни второй сервис, ни ручной UPDATE в проде.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем