Что такое нормализация баз данных и зачем она нужна?

«Что такое нормализация баз данных и зачем она нужна?» — вопрос из категории Базы данных, который задают на 35% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нормализация — это процесс организации структуры реляционной базы данных для минимизации избыточности данных и устранения аномалий при операциях вставки, обновления и удаления. В контексте Node.js приложений нормализация упрощает работу с данными через ORM и снижает вероятность рассогласования.

Проблема ненормализованной структуры (таблица user_orders):

-- Антипаттерн: все данные в одной таблице
CREATE TABLE user_orders (
  order_id INT,
  user_name VARCHAR(100),
  user_email VARCHAR(100),
  user_phone VARCHAR(20),
  product_name VARCHAR(100),
  product_price DECIMAL(10,2),
  product_category VARCHAR(50),
  order_date DATE
);

-- Проблемы:
-- 1. Избыточность: данные пользователя дублируются в каждом заказе
-- 2. Аномалия обновления: изменение email требует обновления всех записей
-- 3. Аномалия удаления: удаление заказа может удалить информацию о продукте

Нормализация до 3NF:

-- 1NF: Атомарные значения, нет повторяющихся групп
CREATE TABLE users (
  user_id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(100) UNIQUE NOT NULL,
  phone VARCHAR(20)
);

CREATE TABLE products (
  product_id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  price DECIMAL(10,2) NOT NULL,
  category_id INT REFERENCES categories(category_id)
);

CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  user_id INT REFERENCES users(user_id),
  order_date DATE DEFAULT CURRENT_DATE,
  status VARCHAR(20) DEFAULT 'pending'
);

CREATE TABLE order_items (
  order_item_id SERIAL PRIMARY KEY,
  order_id INT REFERENCES orders(order_id),
  product_id INT REFERENCES products(product_id),
  quantity INT NOT NULL CHECK (quantity > 0),
  price_at_time DECIMAL(10,2) NOT NULL  -- Историческая цена
);

CREATE TABLE categories (
  category_id SERIAL PRIMARY KEY,
  name VARCHAR(50) UNIQUE NOT NULL
);

Реализация в Node.js с Sequelize:

// Модель User
const User = sequelize.define('User', {
  name: { type: DataTypes.STRING, allowNull: false },
  email: { type: DataTypes.STRING, unique: true, allowNull: false }
});

// Модель Product
const Product = sequelize.define('Product', {
  name: { type: DataTypes.STRING, allowNull: false },
  price: { type: DataTypes.DECIMAL(10,2), allowNull: false }
});

// Модель Order с ассоциациями
const Order = sequelize.define('Order', {
  status: { type: DataTypes.STRING, defaultValue: 'pending' }
});

// Определение связей
User.hasMany(Order, { foreignKey: 'userId' });
Order.belongsTo(User, { foreignKey: 'userId' });
Order.belongsToMany(Product, { through: 'OrderItem' });
Product.belongsToMany(Order, { through: 'OrderItem' });

// Использование нормализованной структуры
const userWithOrders = await User.findOne({
  where: { email: 'john@example.com' },
  include: [{
    model: Order,
    include: [Product]
  }]
});

Преимущества нормализации для Node.js приложений:

  1. Снижение избыточности данных:

    // Вместо хранения всей информации о пользователе в каждом заказе
    // храним только user_id и JOIN'им при необходимости
  2. Устранение аномалий:

    • Вставки: Можно добавить продукт без заказа
    • Обновления: Email пользователя обновляется в одном месте
    • Удаления: Удаление заказа не удаляет информацию о продукте
  3. Улучшение производительности запросов:

    -- Индексы работают эффективнее на нормализованных таблицах
    CREATE INDEX idx_orders_user_id ON orders(user_id);
    CREATE INDEX idx_products_category ON products(category_id);
  4. Гибкость схемы: Легче добавлять новые типы данных и отношения

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

  • Частые сложные JOIN'ы, влияющие на производительность
  • Таблицы для отчетности или аналитики (OLAP)
  • Кэширование часто запрашиваемых агрегированных данных
  • Системы с преимущественно операциями чтения

Видео-ответы