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

Стоит ли устанавливать Primary Key для каждой Entity?

1.0 Junior🔥 301 комментариев
#Базы данных и SQL

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

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

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

Primary Key для каждой Entity

Да, стоит. В 99.9% случаев каждая сущность должна иметь Primary Key (PK). Это фундаментальный принцип проектирования баз данных.

Почему Primary Key обязателен

1. Уникальная идентификация

// БЕЗ Primary Key — проблема
// Таблица users
name      | email              | phone
"John"   | john@example.com   | +1234567890
"John"   | different@email.com | +0987654321
// Какой John? Нельзя различить!

// С Primary Key — решено
id  | name   | email              | phone
1   | John   | john@example.com   | +1234567890
2   | John   | different@email.com | +0987654321
// Ясно: первый John с id=1, второй с id=2

2. Связи между таблицами (Foreign Keys)

// Без PK — невозможно создать FK
// Таблица orders
user_name | product | quantity
"John"    | Laptop  | 1
// К какому John из users это относится? Неизвестно!

// С PK — связь чёткая
// users
id | name
1  | John
2  | Alice

// orders
id | user_id | product | quantity
1  | 1       | Laptop  | 1
2  | 1       | Mouse   | 2
3  | 2       | Monitor | 1
// Ясно: orders 1 и 2 от user_id=1 (John), order 3 от user_id=2 (Alice)

3. Обновления и удаления

// Без PK — опасно
DELETE FROM users WHERE name = 'John';
// Удалятся ВСЕ John'ы! Может быть много!
DELETE FROM users WHERE email = 'john@example.com';
// Полагаясь на email как идентификатор — хрупко

// С PK — безопасно
DELETE FROM users WHERE id = 1;
// Удалится ровно одна запись
UPDATE users SET email = 'newemail@example.com' WHERE id = 1;
// Обновится ровно одна запись

4. Индексирование и производительность

// PK автоматически индексируется
// Запрос по PK — O(1)
SELECT * FROM users WHERE id = 1;
// Очень быстро, даже на таблице с миллионами строк

// Без индекса (без PK) — O(n)
SELECT * FROM users WHERE name = 'John';
// Медленно, нужно просканировать всю таблицу

Типы Primary Keys

1. Single Column PK (самый распространённый)

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // Auto-increment
    private Long id;
    
    private String email;
    private String name;
    // ...
}

// SQL
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL
);

2. UUID как Primary Key

@Entity
@Table(name = "orders")
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)  // UUID v4
    private UUID id;
    
    private String orderNumber;
    private LocalDateTime createdAt;
    // ...
}

// SQL
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    order_number VARCHAR(255) NOT NULL,
    created_at TIMESTAMP NOT NULL
);

// Преимущества UUID:
// - Уникален глобально (не только в таблице)
// - Можно генерировать на клиенте
// - Безопаснее (не угадать следующий ID)

3. Composite PK (составной первичный ключ)

// Редко, но используется для junction tables
@Entity
@Table(name = "student_courses")
public class StudentCourse {
    
    @Id
    private Long studentId;
    
    @Id
    private Long courseId;
    
    private int grade;
    // ...
}

// SQL
CREATE TABLE student_courses (
    student_id BIGINT NOT NULL,
    course_id BIGINT NOT NULL,
    grade INT,
    PRIMARY KEY (student_id, course_id),
    FOREIGN KEY (student_id) REFERENCES students(id),
    FOREIGN KEY (course_id) REFERENCES courses(id)
);

// Гарантирует: каждый студент на каждом курсе только один раз
INSERT INTO student_courses VALUES (1, 101, 95);
INSERT INTO student_courses VALUES (1, 101, 87);  // ERROR: Duplicate PK!

4. Natural Key (бизнес-ключ как PK)

// НЕ рекомендуется, но иногда используется
@Entity
@Table(name = "products")
public class Product {
    
    @Id
    private String sku;  // Stock Keeping Unit (например: "LAPTOP-DELL-001")
    
    private String name;
    private BigDecimal price;
    // ...
}

// Проблемы:
// - SKU может измениться (редко, но бывает)
// - Занимает больше памяти в индексах
// - Foreign Keys становятся больше
// Вывод: лучше использовать суррогатный ключ + уникальный индекс на SKU

Правильный подход: Surrogate Key + Unique Constraint

@Entity
@Table(name = "users", uniqueConstraints = {
    @UniqueConstraint(columnNames = "email"),
    @UniqueConstraint(columnNames = "username")
})
public class User {
    
    // Суррогатный PK (для связей и быстрого доступа)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // Уникальный индекс для бизнес-уникальности
    @Column(unique = true, nullable = false)
    private String email;
    
    @Column(unique = true, nullable = false)
    private String username;
    
    private String name;
    // ...
}

// SQL
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) UNIQUE NOT NULL,
    username VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL
);

// Преимущества:
// - id как PK гарантирует однозначность
// - email и username как UNIQUE гарантируют бизнес-уникальность
// - Foreign Keys маленькие (только id)
// - Можно быстро найти по id И по email

Исключения: когда можно обойтись без PK

1. Таблица логирования (очень редко)

// Таблица только для записи, никогда не обновляется и не удаляется
@Entity
@Table(name = "event_log")
public class EventLog {
    // Можно обойтись без PK, но НЕ рекомендуется
    private LocalDateTime timestamp;
    private String eventType;
    private String details;
}

Даже здесь лучше добавить PK:

@Entity
@Table(name = "event_log")
public class EventLog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  // Помогает удалять старые логи
    
    private LocalDateTime timestamp;
    private String eventType;
    private String details;
}

// Можно удалять старые логи:
DELETE FROM event_log WHERE id < 1000000;
// Без PK пришлось бы удалять по timestamp, что медленнее

2. Таблица кэша (очень редко)

// Таблица для временного хранения (может быть пересоздана)
@Entity
@Table(name = "cache_data")
public class CacheData {
    // Даже здесь лучше добавить PK
    private String cacheKey;
    private String cacheValue;
}

Лучший вариант:

@Entity
@Table(name = "cache_data")
public class CacheData {
    @Id
    private String cacheKey;  // String в качестве PK допустимо
    
    private String cacheValue;
    private LocalDateTime expiresAt;
}

Best Practices для Primary Keys

1. Используйте AUTO_INCREMENT для простоты

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  // 1, 2, 3, ... (простой, понятный)
    // ...
}

2. Используйте UUID для распределённых систем

@Entity
public class Event {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;  // f47ac10b-58cc-4372-a567-0e02b2c3d479
    // ...
}

// UUID помогает при:
// - Разделении БД между сервисами
// - Генерировании ID на клиенте
// - Миграции данных между системами

3. Добавьте индекс на часто используемые поля

@Entity
@Table(name = "users", indexes = {
    @Index(name = "idx_email", columnList = "email"),
    @Index(name = "idx_created_at", columnList = "created_at")
})
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String email;
    private LocalDateTime createdAt;
    // ...
}

4. Не используйте UUID если не нужен

// ПЛОХО: UUID в простом CRUD приложении
@Entity
public class TodoItem {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;  // Оverkill
}

// ХОРОШО: BIGINT если приложение не микросервисное
@Entity
public class TodoItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  // Достаточно
}

5. Не делайте составной PK если можно избежать

// ПЛОХО: составной PK усложняет код
@Entity
@Table(name = "student_courses")
public class StudentCourse {
    @Id
    private Long studentId;
    @Id
    private Long courseId;  // Усложняет запросы и FK
}

// ХОРОШО: суррогатный PK + уникальный индекс
@Entity
@Table(name = "student_courses",
    uniqueConstraints = @UniqueConstraint(columnNames = {"student_id", "course_id"})
)
public class StudentCourse {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  // Простой PK
    
    @Column(name = "student_id")
    private Long studentId;
    
    @Column(name = "course_id")
    private Long courseId;  // Бизнес-ключ защищен UNIQUE
}

Итог

ВопросОтвет
Нужен ли PK для каждой Entity?Да, в 99.9% случаев
Какой PK использовать?BIGINT с AUTO_INCREMENT для простых приложений, UUID для микросервисов
Можно ли обойтись без PK?Теоретически да, практически нет
Нужны ли индексы на другие поля?Да, на часто используемые поля
Составной PK — хорошая идея?Нет, используй суррогатный PK + UNIQUE constraint
Естественный PK лучше суррогатного?Нет, суррогатный надёжнее

Золотое правило: каждая таблица должна иметь Primary Key. Это обеспечит целостность, производительность и безопасность данных.

Стоит ли устанавливать Primary Key для каждой Entity? | PrepBro