Проектирование схемы базы данных веб-приложения

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Проектирование схемы базы данных веб-приложения
Сложная
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Проектирование схемы базы данных веб-приложения

Схема базы данных — фундамент, который сложнее всего менять после запуска. Неправильно нормализованные таблицы, отсутствующие FK или неверные типы данных превращаются в технический долг, который копится годами. Разберём принципы и конкретные решения для типичного веб-приложения.

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

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

Когда денормализация оправдана:

  • Счётчики (comments_count, likes_count) — вместо COUNT JOIN при каждом запросе.
  • Кешированные агрегаты для отчётов (суммы заказов за месяц).
  • Flatten иерархических данных для поиска.

Когда денормализация вредна:

  • Персональные данные, дублированные в нескольких таблицах.
  • Статусы, которые изменяются часто.

Типы данных: частые ошибки

-- Плохо
user_id   INT              -- переполнится на 2.1 млрд записей
price     FLOAT            -- потери точности при финансовых вычислениях
status    INT              -- магические числа, нет domain constraint
created   VARCHAR(30)      -- сортировка строк вместо дат
settings  TEXT             -- нет структуры, нет индекса

-- Хорошо
user_id   BIGINT           -- или UUID
price     DECIMAL(12, 2)   -- точная арифметика
status    VARCHAR(20) CHECK (status IN ('draft', 'published', 'archived'))
created   TIMESTAMPTZ      -- с timezone
settings  JSONB            -- структурированный, индексируемый

TIMESTAMPTZ хранит время в UTC и конвертирует при чтении согласно TimeZone сессии. TIMESTAMP хранит "как есть" — при смене timezone сервера данные теряют смысл.

Первичные ключи: SERIAL vs UUID vs ULID

-- SERIAL (автоинкремент): просто, компактно (8 байт), предсказуемо
id BIGSERIAL PRIMARY KEY

-- UUID v4: уникально глобально, но 16 байт, случайный порядок = фрагментация индекса
id UUID PRIMARY KEY DEFAULT gen_random_uuid()

-- ULID через pg_ulid или генерацию на стороне приложения:
-- лексикографически сортируется по времени, 16 байт
id UUID PRIMARY KEY DEFAULT uuid_generate_v7()  -- PostgreSQL 17+

Для большинства веб-приложений BIGSERIAL — оптимальный выбор. UUID нужен, когда ID генерируются на стороне клиента или нужно скрыть предсказуемость.

Пример: схема интернет-магазина

CREATE TABLE categories (
    id         BIGSERIAL PRIMARY KEY,
    name       VARCHAR(200)  NOT NULL,
    slug       VARCHAR(220)  NOT NULL UNIQUE,
    parent_id  BIGINT        REFERENCES categories(id) ON DELETE SET NULL,
    sort_order INT           NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

CREATE TABLE products (
    id              BIGSERIAL PRIMARY KEY,
    title           VARCHAR(500)   NOT NULL,
    slug            VARCHAR(520)   NOT NULL UNIQUE,
    category_id     BIGINT         NOT NULL REFERENCES categories(id) ON DELETE RESTRICT,
    price           DECIMAL(12, 2) NOT NULL CHECK (price > 0),
    status          VARCHAR(20)    NOT NULL DEFAULT 'draft'
                    CHECK (status IN ('draft', 'published', 'archived')),
    stock           INT            NOT NULL DEFAULT 0 CHECK (stock >= 0),
    specs           JSONB,
    search_vector   TSVECTOR,                -- для full-text search
    created_at      TIMESTAMPTZ    NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ    NOT NULL DEFAULT NOW()
);

CREATE TABLE orders (
    id          BIGSERIAL PRIMARY KEY,
    user_id     BIGINT       NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
    status      VARCHAR(20)  NOT NULL DEFAULT 'pending'
                CHECK (status IN ('pending', 'paid', 'shipped', 'completed', 'cancelled')),
    total       DECIMAL(12, 2) NOT NULL,
    currency    CHAR(3)      NOT NULL DEFAULT 'USD',
    meta        JSONB,                       -- delivery address и т.д.
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

CREATE TABLE order_items (
    id          BIGSERIAL PRIMARY KEY,
    order_id    BIGINT         NOT NULL REFERENCES orders(id)   ON DELETE CASCADE,
    product_id  BIGINT         NOT NULL REFERENCES products(id) ON DELETE RESTRICT,
    quantity    INT            NOT NULL CHECK (quantity > 0),
    unit_price  DECIMAL(12, 2) NOT NULL,     -- цена на момент покупки
    UNIQUE (order_id, product_id)
);

unit_price в order_items — намеренная денормализация: цена продукта изменится со временем, а историческая цена в заказе должна остаться неизменной.

ON DELETE RESTRICT vs CASCADE — правило: CASCADE только когда дочерние записи не имеют смысла без родителя (order_items без order). RESTRICT, когда удаление родителя должно быть явно предотвращено (нельзя удалить категорию с товарами).

Индексы: что добавлять при проектировании

Добавляем сразу при создании схемы:

-- FK columns — всегда, иначе DELETE родителя = seq scan дочерней таблицы
CREATE INDEX idx_products_category_id  ON products (category_id);
CREATE INDEX idx_order_items_order_id  ON order_items (order_id);
CREATE INDEX idx_order_items_product_id ON order_items (product_id);

-- Частые фильтры
CREATE INDEX idx_products_status_created ON products (status, created_at DESC);
CREATE INDEX idx_orders_user_created     ON orders (user_id, created_at DESC);

-- Partial index для активных записей
CREATE INDEX idx_products_published ON products (category_id, created_at DESC)
    WHERE status = 'published';

-- GIN для JSONB
CREATE INDEX idx_products_specs ON products USING GIN (specs);

Аудит и soft delete

Паттерн soft delete:

ALTER TABLE products ADD COLUMN deleted_at TIMESTAMPTZ;
CREATE INDEX idx_products_deleted_at ON products (deleted_at) WHERE deleted_at IS NULL;

Partial index по WHERE deleted_at IS NULL — активные записи индексируются отдельно. Удалённые записи не попадают в индекс и не замедляют запросы.

Паттерн аудита через триггер:

CREATE TABLE audit_log (
    id          BIGSERIAL PRIMARY KEY,
    table_name  VARCHAR(100) NOT NULL,
    row_id      BIGINT       NOT NULL,
    operation   CHAR(1)      NOT NULL CHECK (operation IN ('I', 'U', 'D')),
    old_data    JSONB,
    new_data    JSONB,
    changed_by  BIGINT       REFERENCES users(id),
    changed_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

Сроки

Проектирование схемы для нового проекта (до 15 таблиц, ER-диаграмма, SQL DDL, обсуждение индексов и FK-стратегий): 1–2 дня. Ревью и рефакторинг существующей схемы с выявлением проблем (неправильные типы, отсутствующие FK, избыточные индексы): 1–3 дня в зависимости от объёма.