Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Критерии Primary Key (Первичный ключ)
Primary Key (PK) — это столбец или набор столбцов, которые уникально идентифицируют каждую строку в таблице. Вот основные критерии и требования:
1. Уникальность (Uniqueness)
Каждое значение должно быть уникальным в таблице:
-- ✅ Правильно: id уникален
CREATE TABLE users (
id BIGINT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100)
);
-- Вставка данных
INSERT INTO users (id, email, name) VALUES (1, 'alice@example.com', 'Alice');
INSERT INTO users (id, email, name) VALUES (2, 'bob@example.com', 'Bob');
-- INSERT INTO users (id, email, name) VALUES (1, 'charlie@example.com', 'Charlie');
-- ❌ Ошибка: Duplicate primary key
@Entity
@Table(name = "users")
public class User {
@Id // Уникально идентифицирует
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ❌ Допустимо
private String email; // Может повторяться (без UNIQUE)
// ✅ Правильно
@Column(unique = true)
private String username; // Уникален, но не PK
}
2. Не-null (Not Null)
Значение PK не может быть NULL:
-- ✅ PK всегда NOT NULL
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY NOT NULL, -- PK автоматически NOT NULL
user_id BIGINT NOT NULL,
total_amount DECIMAL(10,2)
);
-- ❌ Ошибка: NULL не разрешен в PK
INSERT INTO orders (order_id, user_id, total_amount)
VALUES (NULL, 1, 100.00); -- Ошибка
-- ❌ Ошибка: NULL для части составного PK
INSERT INTO orders (order_id, user_id, total_amount)
VALUES (1, NULL, 100.00); -- Ошибка (если user_id входит в PK)
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderId; // Автоматически NOT NULL
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user; // NOT NULL
private BigDecimal totalAmount;
}
3. Минимальность
PK должен содержать только необходимые для уникальности столбцы:
-- ❌ Плохо: лишний столбец в PK
CREATE TABLE user_roles (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
created_at TIMESTAMP,
PRIMARY KEY (user_id, role_id, created_at) -- Слишком много
);
-- ✅ Правильно: минимальный PK
CREATE TABLE user_roles (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
created_at TIMESTAMP,
PRIMARY KEY (user_id, role_id) -- Достаточно
);
4. Стабильность (Stability)
Значение PK не должно меняться:
-- ❌ Плохо: email может меняться
CREATE TABLE users (
email VARCHAR(255) PRIMARY KEY, -- Меняется! Плохой выбор
name VARCHAR(100)
);
-- При изменении email нужно обновлять все FK
-- ✅ Правильно: id не меняется
CREATE TABLE users (
id BIGINT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100)
);
// ❌ Плохо
@Entity
public class BadUser {
@Id
private String email; // Может меняться
private String name;
}
// ✅ Правильно
@Entity
public class GoodUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // Никогда не меняется
@Column(unique = true, nullable = false)
private String email;
private String name;
}
5. Простота (Simplicity)
Предпочтительно использовать простой PK (один столбец):
-- ✅ Правильно: простой PK
CREATE TABLE users (
id BIGINT PRIMARY KEY,
email VARCHAR(255),
name VARCHAR(100)
);
-- ⚠️ Допустимо: составной PK (когда необходимо)
CREATE TABLE user_roles (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id)
);
6. Натуральный vs Суррогатный ключ
-- ❌ Натуральный ключ (из данных)
CREATE TABLE countries (
country_code VARCHAR(2) PRIMARY KEY, -- "US", "GB", "FR"
name VARCHAR(100)
);
-- Проблемы: может измениться (хоть редко), сложный для FK
-- ✅ Суррогатный ключ (искусственный)
CREATE TABLE countries (
id BIGINT PRIMARY KEY,
country_code VARCHAR(2) UNIQUE NOT NULL,
name VARCHAR(100)
);
-- Преимущества: никогда не меняется, эффективнее для FK
@Entity
@Table(name = "countries")
public class Country {
// ✅ Суррогатный ключ
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Натуральный ключ (unique, но не PK)
@Column(unique = true, nullable = false, length = 2)
private String countryCode;
private String name;
}
7. Автоинкремент vs UUID
-- ✅ IDENTITY (автоинкремент)
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255),
created_at TIMESTAMP
);
-- Преимущества: компактный, быстрый
-- Недостатки: предсказуемый, проблемы с распределённостью
-- ✅ UUID
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255),
created_at TIMESTAMP
);
-- Преимущества: уникален глобально, непредсказуемый
-- Недостатки: больше памяти (16 vs 8 байт), медленнее
@Entity
public class User {
// IDENTITY - лучше для одного сервера
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// UUID - лучше для распределённых систем
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
// Sequencevalue - в Oracle/PostgreSQL
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "user_sequence")
@SequenceGenerator(name = "user_sequence", sequenceName = "user_seq")
private Long id;
}
8. Составной PK (Composite Key)
-- Составной PK для связи многие-ко-многим
CREATE TABLE user_permissions (
user_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
granted_at TIMESTAMP,
PRIMARY KEY (user_id, permission_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (permission_id) REFERENCES permissions(id)
);
@Entity
@Table(name = "user_permissions")
public class UserPermission {
@EmbeddedId
private UserPermissionId id;
@Embeddable
public static class UserPermissionId implements Serializable {
@Column(name = "user_id")
private Long userId;
@Column(name = "permission_id")
private Long permissionId;
// equals и hashCode обязательны для EmbeddedId
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
}
private LocalDateTime grantedAt;
}
9. Performance критерии
public class PKPerformanceCriteria {
// ✅ Оптимальный размер
// BIGINT (8 bytes) - хороший выбор
// INT (4 bytes) - если гарантированно < 2 млрд
// UUID (16 bytes) - если нужна глобальная уникальность
// ✅ Используй B-Tree индекс (по умолчанию)
// ❌ Избегай текстовых PK (медленнее и больше памяти)
// ✅ Однократное создание с auto_increment
// Избегай пересоздания и миграции PK
}
10. Миграции с PK
// Добавление нового столбца и переключение на него
// БЕЗ ПОТЕРИ ДАННЫХ
// Шаг 1: Добавить новый столбец
// ALTER TABLE users ADD COLUMN user_uuid UUID;
// Шаг 2: Заполнить UUID значения
// UPDATE users SET user_uuid = gen_random_uuid();
// Шаг 3: Сделать NOT NULL
// ALTER TABLE users ALTER COLUMN user_uuid SET NOT NULL;
// Шаг 4: Создать уникальный индекс
// CREATE UNIQUE INDEX idx_user_uuid ON users(user_uuid);
// Шаг 5: Переместить все FK (если они есть)
// Обновить все foreign keys
// Шаг 6: Переключить PK
// ALTER TABLE users DROP CONSTRAINT users_pkey;
// ALTER TABLE users ADD PRIMARY KEY (user_uuid);
// Шаг 7: Удалить старый столбец (опционально)
// ALTER TABLE users DROP COLUMN id;
Чеклист для Primary Key
public class PrimaryKeyChecklist {
// ✅ Каждая таблица имеет PK
// ✅ PK уникален (UNIQUE)
// ✅ PK не может быть NULL
// ✅ PK содержит минимум столбцов
// ✅ PK значения никогда не меняются
// ✅ PK простой тип (BIGINT, UUID, не текст)
// ✅ PK автоматически генерируется (IDENTITY, SEQUENCE)
// ✅ Составной PK используется редко (для M2M)
// ✅ Все FK указывают на PK
// ✅ PK индексируется (автоматически)
// ✅ Есть backup индекс на натуральный ключ (UNIQUE)
}
Примеры правильного проектирования
-- ✅ ХОРОШАЯ ТАБЛИЦА
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL UNIQUE,
username VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE posts (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
title VARCHAR(255) NOT NULL,
content LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE user_roles (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);
Вывод
Основные критерии Primary Key:
- Уникальность — нет дубликатов
- Not Null — всегда есть значение
- Минимальность — только нужные столбцы
- Стабильность — не меняется со временем
- Простота — предпочтительно один столбец
- Суррогатный ключ — специально созданный ID
- Производительность — эффективный тип данных
- Правильное использование FK — связь через PK
Удачный PK — это основа надёжной и производительной БД.