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

Что такое первичный ключ (Primary Key)?

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

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

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

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

Первичный ключ (Primary Key): определение, свойства и практика

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

Определение и свойства

Основные свойства Primary Key

1. Уникальность (Uniqueness) Жчение PK не может повторяться в таблице. Каждая строка имеет свой PK.

CREATE TABLE users (
  id INT PRIMARY KEY,  -- id=1 может быть только один раз
  email VARCHAR(100),
  name VARCHAR(100)
);

-- Попытка вставить дубликат → ERROR
INSERT INTO users VALUES (1, 'john@example.com', 'John');
INSERT INTO users VALUES (1, 'jane@example.com', 'Jane');  -- ERROR!

2. Не NULL (NOT NULL) Значение PK не может быть NULL. Иначе строка не будет однозначно идентифицироваться.

INSERT INTO users VALUES (NULL, 'john@example.com', 'John');  -- ERROR!

3. Неизменяемость (Immutability) В идеале PK не должен меняться, так как на него ссылаются другие таблицы через Foreign Keys.

-- Плохо (нарушает referential integrity)
UPDATE users SET id = 2 WHERE id = 1;
-- Потому что orders.user_id может ссылаться на id=1

4. Минимальность (Minimality) PK должен быть минимальным — не включать "лишние" столбцы.

❌ Плохо (включает лишний столбец):
PRIMARY KEY (user_id, email, created_at)

✅ Хорошо (только уникальный идентификатор):
PRIMARY KEY (user_id)

Типы Primary Keys

1. Простой PK (Single column)

CREATE TABLE products (
  id INT PRIMARY KEY,  -- INT, BIGINT, или UUID
  name VARCHAR(100),
  price DECIMAL(10, 2)
);

Типы значений:

  • AUTO_INCREMENT (MySQL) / SERIAL (PostgreSQL)

    CREATE TABLE orders (
      id SERIAL PRIMARY KEY,  -- 1, 2, 3, ...
      order_date DATE,
      total DECIMAL(10, 2)
    );
    
  • UUID (Universally Unique Identifier)

    CREATE TABLE users (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      email VARCHAR(100),
      name VARCHAR(100)
    );
    -- id = "507f1f77-bcf8-6cd7-9943-9011"
    

Сравнение INT vs UUID:

ПараметрINT/BIGINTUUID
Размер4-8 байт16 байт
读读БыстроМедленнее (индексы больше)
КоллизииSERIAL = уникальноОчень низкая вероятность
Распределённые системыСложно✅ Идеально
Прямой доступ✅ id=42Нужен UUID
SecurityУгадать следующий ID❌ Нельзя угадать

2. Составной PK (Composite Primary Key)

Когда одного столбца недостаточно для уникальности, используется комбинация.

CREATE TABLE student_enrollments (
  student_id INT NOT NULL,
  course_id INT NOT NULL,
  enrollment_date DATE,
  grade CHAR(1),
  PRIMARY KEY (student_id, course_id)  -- Комбинация уникальна
);

-- Можно один и тот же student в разных courses
INSERT INTO student_enrollments VALUES (1, 101, '2025-01-15', 'A');
INSERT INTO student_enrollments VALUES (1, 102, '2025-01-15', 'B');  -- ✅ OK
INSERT INTO student_enrollments VALUES (1, 101, '2025-02-01', 'A');  -- ❌ ERROR! (дубликат)

Другой пример:

CREATE TABLE user_addresses (
  user_id INT NOT NULL,
  address_type VARCHAR(20) NOT NULL,  -- 'home', 'work', 'billing'
  street VARCHAR(255),
  city VARCHAR(100),
  PRIMARY KEY (user_id, address_type)
);

-- User может иметь несколько адресов, но не несколько адресов одного типа
INSERT INTO user_addresses VALUES (1, 'home', '123 Main St', 'NY');
INSERT INTO user_addresses VALUES (1, 'work', '456 Office Blvd', 'NY');  -- ✅ OK
INSERT INTO user_addresses VALUES (1, 'home', '789 Other St', 'NY');  -- ❌ ERROR!

Primary Key vs Unique Key

ПараметрPrimary KeyUnique Key
КоличествоТолько 1Много
NULL допустим❌ Нет✅ Да (может быть много NULL)
Индекс✅ Автоматически✅ Автоматически
Foreign Key✅ Может быть ссылка❌ Обычно нет
Примерidemail (уникален, но может быть NULL в legacy)
CREATE TABLE users (
  id INT PRIMARY KEY,  -- Один PK
  email VARCHAR(100) UNIQUE,  -- Unique key (но может быть NULL)
  username VARCHAR(100) UNIQUE,  -- Ещё один unique key
  phone VARCHAR(20)  -- Обычный столбец
);

INSERT INTO users VALUES (1, 'john@example.com', 'john', '555-1234');
INSERT INTO users VALUES (2, NULL, 'jane', '555-5678');  -- ✅ OK (NULL допустим в UNIQUE)
INSERT INTO users VALUES (3, NULL, 'bob', '555-9999');  -- ✅ OK (несколько NULL в UNIQUE)
INSERT INTO users VALUES (4, 'john@example.com', 'john2', '555-0000');  -- ❌ ERROR (дубликат email)

Primary Key и Foreign Key

Primary Key в одной таблице ссылается на другие таблицы через Foreign 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
);

-- user_id в orders ссылается на id (PK) в users

Каскадное удаление (ON DELETE CASCADE):

-- Если удалить пользователя, все его заказы удалятся тоже
DELETE FROM users WHERE id = 1;
-- → Все строки с user_id=1 в orders удалятся автоматически

Выбор Primary Key на практике

Сценарий 1: Таблица с простым идентификатором

CREATE TABLE articles (
  id BIGSERIAL PRIMARY KEY,  -- AUTO_INCREMENT
  title VARCHAR(255) NOT NULL,
  slug VARCHAR(255) UNIQUE NOT NULL,  -- URL-friendly
  content TEXT,
  author_id INT NOT NULL REFERENCES users(id),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Запрос:

SELECT * FROM articles WHERE id = 42;  -- Быстро (индекс на PK)

Сценарий 2: Распределённая система (микросервисы)

CREATE TABLE events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  event_type VARCHAR(50),
  user_id UUID,  -- Может быть из другой системы
  data JSONB,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

Преимущества UUID:

  • Генерируется на клиенте (не нужно ждать DB)
  • Работает в распределённых системах
  • Нельзя угадать следующий ID (security)

Сценарий 3: Слабо связанная таблица (junction table)

CREATE TABLE student_courses (
  student_id INT NOT NULL,
  course_id INT NOT NULL,
  PRIMARY KEY (student_id, course_id),
  FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
  FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE
);

-- Составной PK является и составным FK

Частые ошибки

❌ Ошибка 1: Нет Primary Key

CREATE TABLE logs (
  message VARCHAR(255),
  level VARCHAR(10),
  created_at TIMESTAMP
  -- Нет PK! Может быть несколько идентичных строк
);

Исправление:

CREATE TABLE logs (
  id BIGSERIAL PRIMARY KEY,  -- Добавить PK
  message VARCHAR(255),
  level VARCHAR(10),
  created_at TIMESTAMP
);

❌ Ошибка 2: Изменяемый Primary Key

-- Плохо: использовать email как PK
CREATE TABLE users (
  email VARCHAR(100) PRIMARY KEY,  -- Может измениться
  name VARCHAR(100)
);

-- Если user изменит email:
-- 1. Нарушается referential integrity (orders.user_email)
-- 2. Сложный UPDATE CASCADE

Исправление:

CREATE TABLE users (
  id INT PRIMARY KEY,  -- Стабильный ID
  email VARCHAR(100) UNIQUE,  -- Уникален, но может измениться
  name VARCHAR(100)
);

❌ Ошибка 3: Слишком большой составной PK

CREATE TABLE transactions (
  user_id INT NOT NULL,
  product_id INT NOT NULL,
  timestamp TIMESTAMP NOT NULL,
  amount DECIMAL(10, 2),
  PRIMARY KEY (user_id, product_id, timestamp)  -- Слишком большой
);

Исправление:

CREATE TABLE transactions (
  id BIGSERIAL PRIMARY KEY,  -- Простой PK
  user_id INT NOT NULL,
  product_id INT NOT NULL,
  timestamp TIMESTAMP NOT NULL,
  amount DECIMAL(10, 2),
  INDEX (user_id, product_id)  -- Для быстрого поиска
);

Primary Key и производительность

Индексирование: Primary Key автоматически создаёт индекс (B-tree). Это ускоряет:

  • SELECT WHERE id = X
  • JOIN ON table.id = other.table.id
  • DELETE FROM table WHERE id = X
-- Быстро (PK индекс используется)
SELECT * FROM users WHERE id = 42;

-- Медленно (full table scan)
SELECT * FROM users WHERE email = 'john@example.com';  
-- РЕШЕНИЕ: CREATE INDEX idx_users_email ON users(email);

Заключение

Первичный ключ — это:

  1. Обязателен в каждой таблице
  2. Уникален и не NULL по определению
  3. Стабилен (не должен меняться)
  4. Минимален (ровно столько столбцов, сколько нужно)
  5. Источник истины для идентификации строк

Выбор между INT vs UUID зависит от архитектуры (монолит → INT, микросервисы → UUID). Главное — выбрать и придерживаться стратегии.

Что такое первичный ключ (Primary Key)? | PrepBro