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

Какие знаешь требования к первичному ключу в БД?

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

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

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

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

Требования к первичному ключу в БД

Первичный ключ (Primary Key, PK) — это столбец или набор столбцов, которые уникально идентифицируют каждую строку в таблице. Первичный ключ имеет строгие требования.

Основные требования

1. Уникальность (Uniqueness)

Каждое значение первичного ключа должно быть уникальным в таблице.

CREATE TABLE users (
    id INT PRIMARY KEY,  -- Каждый id должен быть уникальным
    name VARCHAR(100)
);

INSERT INTO users (id, name) VALUES (1, 'John');
INSERT INTO users (id, name) VALUES (1, 'Jane'); 
-- ❌ Ошибка: duplicate key value violates unique constraint "users_pkey"

Как работает: База данных автоматически создаёт уникальный индекс на первичном ключе.

-- Эти две команды эквивалентны:
CREATE TABLE users (
    id INT PRIMARY KEY
);

-- И
CREATE TABLE users (
    id INT UNIQUE NOT NULL
);
CREATE INDEX idx_id ON users(id);

2. Неопустимость (NOT NULL)

Первичный ключ не может быть NULL. Это обязательное требование.

CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    customer_id INT
);

INSERT INTO orders (order_id, customer_id) VALUES (NULL, 1);
-- ❌ Ошибка: null value in column "order_id" violates not-null constraint

INSERT INTO orders (order_id, customer_id) VALUES (1, NULL); -- ✅ OK

SQL гарантирует это:

CREATE TABLE orders (
    order_id INT NOT NULL,  -- Явно NOT NULL
    PRIMARY KEY (order_id)
);

3. Постоянство (Immutability)

Первичный ключ не должен изменяться после создания записи. Изменение PK может разрушить связи (Foreign Keys).

// ❌ Плохо: первичный ключ изменяется
User user = repository.findById(1);
user.setId(999); // ❌ Опасно!
repository.save(user);

// ✅ Хорошо: первичный ключ не трогаешь
User user = repository.findById(1);
user.setName("New Name"); // OK
repository.save(user);

Почему это проблема:

CREATE TABLE users (id INT PRIMARY KEY);
CREATE TABLE orders (order_id INT, user_id INT REFERENCES users(id));

-- Если изменить user.id с 1 на 999, все заказы потеряют связь

4. Минимальность (Minimality)

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

-- ❌ Плохо: избыточный составной ключ
CREATE TABLE user_profiles (
    id INT,
    username VARCHAR(100),
    email VARCHAR(100),
    age INT,
    PRIMARY KEY (id, username, email)  -- id один уже уникален!
);

-- ✅ Хорошо: минимальный ключ
CREATE TABLE user_profiles (
    id INT PRIMARY KEY,  -- Только id
    username VARCHAR(100),
    email VARCHAR(100),
    age INT
);

5. Постоянство значений (Permanence)

Значение первичного ключа не должно меняться. Если значение часто меняется, это не должен быть первичный ключ.

-- ❌ Плохо: email может измениться
CREATE TABLE users (
    email VARCHAR(100) PRIMARY KEY,
    name VARCHAR(100)
);
-- Если пользователь сменит email, придётся обновлять все связи

-- ✅ Хорошо: id никогда не меняется
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE,
    name VARCHAR(100)
);

Типы первичных ключей

1. Simple Primary Key (Простой ключ)

Первичный ключ состоит из одного столбца.

CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

-- Или
CREATE TABLE users (
    id INT,
    name VARCHAR(100),
    PRIMARY KEY (id)
);

2. Composite Primary Key (Составной ключ)

Первичный ключ состоит из нескольких столбцов.

CREATE TABLE student_courses (
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    grade CHAR(1),
    PRIMARY KEY (student_id, course_id)  -- Оба столбца вместе уникальны
);

-- Можно вставить:
INSERT INTO student_courses VALUES (1, 1, 'A');  -- OK
INSERT INTO student_courses VALUES (1, 2, 'B');  -- OK
INSERT INTO student_courses VALUES (2, 1, 'C');  -- OK
INSERT INTO student_courses VALUES (1, 1, 'D');  -- ❌ Дублирование
// В JPA
@Entity
@Table(name = "student_courses")
public class StudentCourse {
    @EmbeddedId
    private StudentCourseId id;
    
    private Character grade;
}

@Embeddable
public class StudentCourseId implements Serializable {
    @Column(name = "student_id")
    private Integer studentId;
    
    @Column(name = "course_id")
    private Integer courseId;
    
    // equals() и hashCode()
}

Выбор первичного ключа

1. Auto-increment (Self-generating)

CREATE TABLE users (
    id SERIAL PRIMARY KEY,  -- PostgreSQL
    name VARCHAR(100)
);

-- MySQL
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100)
);

Плюсы:

  • Простой
  • Небольшой размер
  • Быстро генерируется

Минусы:

  • Последовательный (видны масштабы системы)
  • Могут быть gap'ы при откате транзакций

2. UUID (Universally Unique Identifier)

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100)
);
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
}

Плюсы:

  • Глобально уникальный
  • Не раскрывает масштабы
  • Подходит для распределённых систем

Минусы:

  • Больший размер (16 байт vs 4 байта для int)
  • Медленнее индексирование
  • Случайный (плохо для кэша)

3. Business Key

CREATE TABLE products (
    sku VARCHAR(50) PRIMARY KEY,  -- Stock Keeping Unit
    name VARCHAR(100),
    price DECIMAL(10, 2)
);

Плюсы:

  • Имеет смысл (что это такое)
  • Нет необходимости в отдельном ID

Минусы:

  • Может изменяться (редко, но случается)
  • Большего размера

4. Surrogate Key + Natural Key

Лучший подход — использовать оба:

CREATE TABLE users (
    id INT PRIMARY KEY,              -- Surrogate key (для FK)
    email VARCHAR(100) UNIQUE,       -- Natural key (для поиска)
    name VARCHAR(100)
);
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  // Surrogate PK
    
    @Column(unique = true)
    private String email;  // Natural key
    
    private String name;
}

Foreign Key и Primary Key

CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT NOT NULL,
    amount DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

Требования:

  • Foreign Key должен ссылаться на Primary Key
  • Типы должны совпадать (int ↔ int, UUID ↔ UUID)

Изменение Primary Key

Изменить Primary Key сложно (нарушает все Foreign Keys):

-- Очень опасно!
ALTER TABLE users DROP CONSTRAINT users_pkey;
ALTER TABLE users ADD PRIMARY KEY (email);
-- Все FOREIGN KEY будут нарушены!

Правильный способ:

  1. Создать новый PK (например, id)
  2. Обновить все Foreign Keys
  3. Удалить старый PK

Checklist при создании Primary Key

  • Уникален ли? Каждое значение встречается один раз
  • Не NULL? Все значения заполнены
  • Постоянен ли? Не будет меняться
  • Минимален ли? Нет избыточных столбцов
  • Правильный тип? INT, UUID, VARCHAR (редко)
  • Индексирован ли? Автоматически (PRIMARY KEY создаёт индекс)
  • Foreign Keys обновлены? Все внешние ключи ссылаются правильно

Примеры ошибок

-- ❌ Ошибка 1: Nullable PK
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) NULL  -- ❌ Может быть NULL
);

-- ❌ Ошибка 2: Изменяемый PK
CREATE TABLE users (
    email VARCHAR(100) PRIMARY KEY  -- ❌ Email может измениться
);

-- ❌ Ошибка 3: Избыточный составной ключ
CREATE TABLE users (
    id INT,
    email VARCHAR(100),
    name VARCHAR(100),
    PRIMARY KEY (id, email, name)  -- ❌ Только id достаточно
);

-- ✅ Правильно
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE,
    name VARCHAR(100)
);

Выводы

  1. Уникальность — главное требование
  2. NOT NULL — обязательно
  3. Постоянство — первичный ключ не меняется
  4. Минимальность — только необходимые столбцы
  5. Выбирай surrogate key (id SERIAL) в большинстве случаев
  6. UUID для распределённых систем (микросервисы)
  7. Естественные ключи редко используются как PK
  8. Всегда включай уникальные индексы на natural keys (email, username)
  9. Никогда не меняй PK после создания записи
  10. Обновляй Foreign Keys, если меняешь структуру
Какие знаешь требования к первичному ключу в БД? | PrepBro