Какие знаешь требования к первичному ключу в БД?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Требования к первичному ключу в БД
Первичный ключ (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 будут нарушены!
Правильный способ:
- Создать новый PK (например, id)
- Обновить все Foreign Keys
- Удалить старый 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)
);
Выводы
- Уникальность — главное требование
- NOT NULL — обязательно
- Постоянство — первичный ключ не меняется
- Минимальность — только необходимые столбцы
- Выбирай surrogate key (id SERIAL) в большинстве случаев
- UUID для распределённых систем (микросервисы)
- Естественные ключи редко используются как PK
- Всегда включай уникальные индексы на natural keys (email, username)
- Никогда не меняй PK после создания записи
- Обновляй Foreign Keys, если меняешь структуру