← Назад к вопросам

Когда будешь использовать синтетический ключ?

1.7 Middle🔥 171 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Синтетический ключ в базах данных: когда и почему

Определение

Синтетический ключ — это искусственно созданный уникальный идентификатор, который не имеет семантического значения в предметной области. Он существует исключительно для идентификации записей. Противоположность — естественный ключ, который состоит из атрибутов реальных данных (например, номер паспорта, ISBN книги).

Основные варианты синтетических ключей

Автоинкрементирующееся целое число

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) UNIQUE,
    name VARCHAR(100)
);

UUID (Universal Unique Identifier)

CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE
);

Хеширование с солью

CREATE TABLE products (
    id CHAR(64) PRIMARY KEY,  -- SHA-256
    sku VARCHAR(50) UNIQUE,
    name VARCHAR(200)
);

Когда использовать синтетический ключ

1. Нет хорошего естественного ключа

Ситуация: Если у сущности нет полей с уникальными, стабильными значениями, создаешь синтетический ключ.

// Таблица событий логирования
CREATE TABLE events (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id UUID NOT NULL,
    action VARCHAR(50),
    timestamp TIMESTAMP WITH TIME ZONE,
    ip_address INET
);
// Нет естественного ключа — данные могут дублироваться

2. Естественный ключ слишком сложный

Ситуация: Есть набор полей, которые уникально идентифицируют запись, но комбинация громоздкая.

-- Вариант 1: Естественный ключ (громоздко)
CREATE TABLE class_enrollments (
    school_id INT,
    academic_year INT,
    class_letter CHAR(1),
    student_id INT,
    enrollment_date DATE,
    PRIMARY KEY (school_id, academic_year, class_letter, student_id)
);

-- Вариант 2: Синтетический ключ (лучше)
CREATE TABLE class_enrollments (
    enrollment_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    school_id INT NOT NULL,
    academic_year INT NOT NULL,
    class_letter CHAR(1) NOT NULL,
    student_id INT NOT NULL,
    enrollment_date DATE NOT NULL,
    UNIQUE (school_id, academic_year, class_letter, student_id)
);

Преимущества вариант 2:

  • Внешние ключи становятся короче: enrollment_id INT вместо 4 полей
  • Индексы на таблице меньше по размеру
  • JOIN'ы работают быстрее
  • Удобнее ссылаться из других таблиц

3. Естественный ключ может измениться

Ситуация: Данные, которые идентифицируют запись, могут измениться по требованиям бизнеса.

// Плохо: email как первичный ключ
CREATE TABLE users (
    email VARCHAR(255) PRIMARY KEY,
    name VARCHAR(100)
);
// Проблема: если пользователь изменит email, нужно обновить
// все внешние ключи в других таблицах — дорого!

// Хорошо: синтетический ключ
CREATE TABLE users (
    user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL
);
// email может измениться, user_id остаётся стабильным

4. Распределённые системы

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

// UUID идеален для распределённых систем
CREATE TABLE posts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    author_id UUID NOT NULL,
    title VARCHAR(300),
    created_at TIMESTAMP WITH TIME ZONE
);
// UUID гарантирует уникальность без центрального счётчика

Сравнение синтетических ключей

ВидРазмерСкоростьРаспределениеПрактика
AUTO_INCREMENT8 байт⚡⚡⚡❌ НетOLTP, однозначное владение
UUID16 байт⚡⚡✓ ДаREST API, микросервисы
ULID16 байт⚡⚡⚡✓ ДаСовременный стандарт
Snowflake ID8 байт⚡⚡⚡✓ ДаВысоконагруженные системы

Практический пример: E-commerce

// Слой domain
public class Order {
    private final OrderId id;  // Синтетический ключ UUID
    private final UserId userId;  // Ссылка на пользователя
    private final LocalDateTime createdAt;
    private List<OrderItem> items;
    
    public Order(UserId userId, List<OrderItem> items) {
        this.id = new OrderId(UUID.randomUUID());
        this.userId = userId;
        this.items = items;
        this.createdAt = LocalDateTime.now(UTC);
    }
    
    public OrderId getId() { return id; }
}

// Слой persistence
public class OrderRepository {
    // SQL: PRIMARY KEY (id), UNIQUE (user_id, created_at, ...natural key...)
    // Синтетический id = быстрые JOIN'ы
    // Естественный ключ для бизнес-правил
}

Когда НЕ использовать синтетический ключ

1. Если есть хороший, стабильный естественный ключ

CREATE TABLE countries (
    code CHAR(2) PRIMARY KEY,  -- ISO 3166-1 alpha-2
    name VARCHAR(100)
);
-- Код страны никогда не меняется, идеален как ключ

2. Если это замедляет запросы

-- Плохо: лишний JOIN
SELECT o.* FROM orders o
WHERE o.user_id = 123
AND o.created_at BETWEEN '2024-01-01' AND '2024-01-31';

-- Хорошо: если часто ищешь по (user_id, month)
CREATE TABLE orders (
    user_id INT,
    year_month DATE,
    order_seq INT,
    ...
    PRIMARY KEY (user_id, year_month, order_seq)
);

3. Данные распределены по разным таблицам (нормализация)

CREATE TABLE taxonomies (
    category_id INT PRIMARY KEY,  -- Может быть естественный ключ
    category_name VARCHAR(100) UNIQUE
);
CREATE TABLE items (
    item_id INT PRIMARY KEY,
    category_id INT REFERENCES taxonomies(category_id)
);

Вывод

Используй синтетический ключ, когда:

  1. Нет хорошего естественного ключа — данные уникальны только по комбинации полей
  2. Естественный ключ сложный — 3+ полей в ключе
  3. Данные могут измениться — email, номер счёта, реквизиты
  4. Система распределённая — UUID для микросервисов, Snowflake ID для высоконагруженных
  5. Нужна простота и производительность — SHORT FOREIGN KEYS

Лучшая практика:

  • Первичный ключ = синтетический (для производительности)
  • Уникальный индекс на естественном ключе (для бизнес-правил)
  • Такой подход гибкий и быстрый