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

Что такое ForeignKey в реляционной БД?

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

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

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

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

Foreign Key (Внешний ключ) в реляционных БД

Foreign Key — это один из фундаментальных концептов реляционных баз данных. Это механизм обеспечения целостности данных и связей между таблицами.

Базовая идея

Foreign Key (FK) — это колонка или набор колонок в одной таблице, которые ссылаются на Primary Key (PK) в другой таблице.

-- Таблица пользователей
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100)
);

-- Таблица заказов
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id)
    --          ^                         ^
    --      FK в этой таблице    PK в другой таблице
);

Что гарантирует Foreign Key

1. Referential Integrity (целостность ссылок)

INSERT INTO orders (id, user_id, amount) VALUES (1, 999, 100);
-- Ошибка: no user with id=999!
-- FK гарантирует что user_id указывает на существующего пользователя

INSERT INTO orders (id, user_id, amount) VALUES (1, 1, 100);
-- OK если user с id=1 существует

2. Предотвращение orphan записей

-- Без FK
DELETE FROM users WHERE id = 1;
-- В таблице orders остаются заказы с user_id=1
-- Это orphan records (сирота) - заказы без хозяина

-- С FK и ON DELETE CASCADE
DELETE FROM users WHERE id = 1;
-- Все заказы этого пользователя тоже удаляются!

Cascading Actions (Каскадные действия)

ON DELETE CASCADE:

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

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

ON DELETE SET NULL:

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,  -- может быть NULL
    amount DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

-- Если удалю пользователя - заказы остаются но с user_id=NULL
DELETE FROM users WHERE id = 1;
-- orders с user_id=1 становятся (id, NULL, amount)

ON DELETE RESTRICT (по умолчанию):

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT
);

-- Не могу удалить пользователя если есть его заказы
DELETE FROM users WHERE id = 1;  -- Ошибка!
-- "Cannot delete or update a parent row: a foreign key constraint fails"

ON DELETE NO ACTION:

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE NO ACTION
);

-- Похож на RESTRICT (в большинстве СУБД это одно и то же)

ON UPDATE CASCADE:

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE
);

-- Если изменю PK пользователя - изменятся все его заказы
UPDATE users SET id = 1000 WHERE id = 1;
-- Все orders с user_id=1 становятся user_id=1000

Composite Foreign Key (составной ключ)

CREATE TABLE departments (
    company_id INT,
    dept_id INT,
    name VARCHAR(100),
    PRIMARY KEY (company_id, dept_id)
);

CREATE TABLE employees (
    id INT PRIMARY KEY,
    company_id INT,
    dept_id INT,
    name VARCHAR(100),
    FOREIGN KEY (company_id, dept_id) REFERENCES departments(company_id, dept_id)
    --           ^                                            ^
    --      Both columns ссылаются на оба PK колонки
);

Self-referencing Foreign Key

-- Таблица категорий (могут быть подкатегории)
CREATE TABLE categories (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    parent_id INT,
    FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);

-- Пример данных:
-- id=1, name='Electronics', parent_id=NULL
-- id=2, name='Laptops', parent_id=1  (подкатегория Electronics)
-- id=3, name='Phones', parent_id=1   (подкатегория Electronics)

Foreign Key в Java с JPA/Hibernate

@Entity
@Table(name = "users")
public class User {
    @Id
    private Long id;
    
    @Column(name = "name")
    private String name;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();
}

@Entity
@Table(name = "orders")
public class Order {
    @Id
    private Long id;
    
    @Column(name = "amount")
    private BigDecimal amount;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    // ^                ^              ^
    // annotation    FK column     NOT NULL constraint
    private User user;
}

// Автоматическое создание заказа:
User user = new User();
Order order = new Order();
order.setUser(user);  // Set FK
user.getOrders().add(order);
userRepository.save(user);  // Сохраняет оба

// Удаление с каскадом:
userRepository.delete(user);  // Все заказы тоже удаляются

Плюсы Foreign Keys

  1. Целостность данных — БД гарантирует что ссылки валидны
  2. Предотвращение orphan records — нельзя создать заказ без пользователя
  3. Удобный каскадный delete — не нужно вручную удалять зависимые записи
  4. Оптимизация запросов — БД знает о связях и может использовать это
  5. Документирование — FK показывают структуру данных

Минусы Foreign Keys

  1. Performance overhead — проверки и индексы занимают ресурсы
  2. Сложнее с тестированием — нужно создавать данные в правильном порядке
  3. Затрудняет массовые операции — могут блокировать друг друга
  4. Миграции БД сложнее — нужно учитывать зависимости
  5. Некоторые СУБД медленнее с FK — особенно с большим количеством

Альтернативы и компромиссы

Без Foreign Keys (больше гибкости):

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,  -- Просто колонка, без FK constraint
    amount DECIMAL(10, 2)
);

-- Можно вставить любой user_id даже если пользователя нет
-- Целостность контролируется на уровне приложения
-- Плюс: быстрее, гибче
-- Минус: может быть мусор

Частичные FK (compromises):

-- FK только на reading side (SELECT)
-- Миграции и bulk inserts без FK
-- Потом включаем FK для проверки
ALTER TABLE orders 
ADD CONSTRAINT fk_user 
FOREIGN KEY (user_id) REFERENCES users(id);

Best Practices

  1. Всегда использовать FK в production для целостности
  2. Выбирай правильный ON DELETE action (CASCADE vs SET NULL vs RESTRICT)
  3. Индексируй FK колонки — помогает производительности
  4. В тестах отключай FK — если медленнее загружать тестовые данные
  5. Документируй отношения — нарисуй диаграмму ER
  6. Используй ON UPDATE CASCADE осторожно — может быть дорого

Проверка FK в PostgreSQL

-- Посмотреть все FK в таблице
SELECT constraint_name, table_name, column_name, foreign_table_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = 'orders';

-- Отключить FK проверку (осторожно!)
ALTER TABLE orders DISABLE TRIGGER ALL;

-- Включить FK проверку
ALTER TABLE orders ENABLE TRIGGER ALL;

Заключение

Foreign Keys — это критичный механизм для обеспечения целостности в реляционных БД. Они гарантируют что данные остаются консистентными. Важно выбирать правильные каскадные действия и индексировать FK для производительности.