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

Какие знаешь критерии Primary Key?

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

Комментарии (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:

  1. Уникальность — нет дубликатов
  2. Not Null — всегда есть значение
  3. Минимальность — только нужные столбцы
  4. Стабильность — не меняется со временем
  5. Простота — предпочтительно один столбец
  6. Суррогатный ключ — специально созданный ID
  7. Производительность — эффективный тип данных
  8. Правильное использование FK — связь через PK

Удачный PK — это основа надёжной и производительной БД.

Какие знаешь критерии Primary Key? | PrepBro