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

В чем разница между уникальным индексом и первичным ключом?

1.0 Junior🔥 141 комментариев
#Базы данных (SQL)

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

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

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

Различия между уникальным индексом и первичным ключом

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

1. Первичный ключ (Primary Key)

Определение: Первичный ключ — это колонка или набор колонок, которые уникально идентифицируют каждую строку в таблице.

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True)  # Первичный ключ
    name = Column(String)
    email = Column(String)

engine = create_engine("postgresql://localhost/db")
Base.metadata.create_all(engine)

Характеристики:

  • Не может быть NULL
  • Гарантирует уникальность строки
  • Может быть только один на таблицу
  • Автоматически индексируется
  • Используется для связей между таблицами (Foreign Keys)
  • Служит идентификатором записи

2. Уникальный индекс (Unique Index/Unique Constraint)

Определение: Уникальный индекс гарантирует, что все значения в колонке (или наборе колонок) уникальны.

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True)
    email = Column(String, unique=True)  # Уникальный индекс
    phone = Column(String)

# Или явно через UniqueConstraint
from sqlalchemy import UniqueConstraint

class Product(Base):
    __tablename__ = "products"
    
    id = Column(Integer, primary_key=True)
    sku = Column(String)  # Stock Keeping Unit
    
    __table_args__ = (
        UniqueConstraint('sku', name='uq_product_sku'),
    )

Характеристики:

  • Может быть NULL
  • Может быть несколько на таблицу
  • Автоматически индексируется
  • Только гарантирует уникальность значений
  • Не используется для связей между таблицами

3. Сравнение

АспектPrimary KeyUnique Index
NULL значенияНе допускаютсяДопускаются
КоличествоОдин на таблицуНесколько
ИндексированиеАвтоматическоеАвтоматическое
Foreign KeyМожно использоватьНельзя
НазначениеИдентификатор строкиГарантия уникальности
ИзменяемостьОбычно не меняетсяМожет меняться
ПроизводительностьОптимизированаХорошая

4. Практические примеры

Пример 1: Таблица пользователей

class User(Base):
    __tablename__ = "users"
    
    # Primary Key — уникально идентифицирует пользователя
    id = Column(Integer, primary_key=True)
    
    # Unique Index — email должен быть уникальным
    email = Column(String, unique=True, nullable=False)
    
    # Unique Index — username должен быть уникальным
    username = Column(String, unique=True, nullable=False)
    
    name = Column(String, nullable=False)
    created_at = Column(DateTime, default=datetime.now)

Пример 2: Таблица продуктов с композитным ключом

from sqlalchemy import UniqueConstraint

class Product(Base):
    __tablename__ = "products"
    
    id = Column(Integer, primary_key=True)
    
    # SKU (Stock Keeping Unit) должен быть уникальным
    sku = Column(String, nullable=False)
    warehouse_id = Column(Integer, nullable=False)
    
    # Уникальность по двум колонкам одновременно
    __table_args__ = (
        UniqueConstraint('sku', 'warehouse_id', name='uq_sku_warehouse'),
    )

Пример 3: Таблица с несколькими уникальными индексами

class Account(Base):
    __tablename__ = "accounts"
    
    id = Column(Integer, primary_key=True)  # Primary Key
    
    # Несколько уникальных индексов
    email = Column(String, unique=True, nullable=False)
    username = Column(String, unique=True, nullable=False)
    phone = Column(String, unique=True)  # Может быть NULL
    api_key = Column(String, unique=True)

5. NULL в уникальном индексе

-- Важный момент: несколько NULL значений в уникальном индексе
CREATE TABLE products (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    description VARCHAR(500) UNIQUE
);

INSERT INTO products VALUES (1, 'Product 1', NULL);
INSERT INTO products VALUES (2, 'Product 2', NULL);
INSERT INTO products VALUES (3, 'Product 3', NULL);

-- Это будет разрешено!
-- NULL != NULL в SQL, поэтому несколько NULL не нарушает уникальность
class Product(Base):
    __tablename__ = "products"
    
    id = Column(Integer, primary_key=True)
    description = Column(String, unique=True)  # Может быть несколько NULL

# В БД это будет работать
session.add(Product(id=1, description=None))
session.add(Product(id=2, description=None))
session.add(Product(id=3, description=None))
session.commit()  # Успешно!

6. Использование в связях между таблицами

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    username = Column(String, unique=True)

class Post(Base):
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True)
    title = Column(String)
    
    # Foreign Key может ссылаться только на Primary Key
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)

# Это работает (user_id -> users.id)
# Но если попытаться сделать Foreign Key на username (unique index):
# post_user_username = Column(String, ForeignKey('users.username'))
# Это тоже будет работать в некоторых БД, но не рекомендуется

7. Индексирование производительности

Primary Key:

-- Быстрый поиск по ID
SELECT * FROM users WHERE id = 123;
-- O(log n) благодаря индексу Primary Key

Unique Index:

-- Быстрый поиск по email
SELECT * FROM users WHERE email = 'user@example.com';
-- O(log n) благодаря уникальному индексу

8. Когда использовать каждый

Primary Key используй для:

  • Уникальной идентификации строки
  • Связей между таблицами (Foreign Keys)
  • Основного способа обращения к записи
  • Обычно это ID или UUID

Unique Index используй для:

  • Email пользователя
  • Username или логин
  • SKU товара
  • API ключи
  • Любые другие поля, которые должны быть уникальны

9. Лучшие практики

Правило 1: Всегда наличествует Primary Key

class GoodTable(Base):
    __tablename__ = "good_table"
    id = Column(Integer, primary_key=True)
    # ... другие колонки

# Никогда не создавай таблицу без Primary Key

Правило 2: Используй Natural vs Surrogate Keys

# Surrogate Key (суррогатный) — искусственный ID
class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)  # Surrogate Key
    email = Column(String, unique=True)  # Natural Key
    username = Column(String, unique=True)  # Natural Key

# Surrogate Keys лучше для Foreign Keys

Правило 3: Composite Primary Key

from sqlalchemy import PrimaryKeyConstraint

class OrderItem(Base):
    __tablename__ = "order_items"
    
    order_id = Column(Integer, nullable=False)
    product_id = Column(Integer, nullable=False)
    quantity = Column(Integer)
    
    __table_args__ = (
        PrimaryKeyConstraint('order_id', 'product_id'),
    )

10. Вывод

Первичный ключ — это фундамент таблицы, уникально идентифицирующий каждую строку и используемый для связей между таблицами. Уникальный индекс — это дополнительное ограничение для гарантии уникальности других важных полей, которые не являются основными идентификаторами. Одна таблица может иметь только один первичный ключ, но несколько уникальных индексов.