Хорошо ли разбивать таблицы в базе данных?

«Хорошо ли разбивать таблицы в базе данных?» — вопрос из категории Базы данных, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Разбиение таблиц (нормализация) — это компромисс между целостностью данных и производительностью. В моей практике я применяю нормализацию осознанно, в зависимости от требований конкретного проекта.

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

  1. Устранение аномалий данных — когда обновление должно затрагивать только одно место
  2. Сложные бизнес-правила — разные сущности с разными жизненными циклами
  3. Частые изменения структуры — легче менять небольшие таблицы

Пример нормализованной структуры для интернет-магазина:

-- Основные таблицы
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE addresses (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    address_line TEXT NOT NULL,
    city VARCHAR(100) NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    address_id INT NOT NULL,
    status ENUM('pending', 'processing', 'shipped', 'delivered'),
    total DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (address_id) REFERENCES addresses(id)
);

Когда стоит денормализовать:

  • Частые аналитические запросы — денормализация ускоряет SELECT
  • Системы чтения (read-heavy) — кэширование вычисленных полей
  • Микросервисная архитектура — каждый сервис владеет своими данными

Мой подход:

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

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