Насколько обязательно строить индекс для внешнего ключа (foreign key)?

Ответ

Строго обязательно в подавляющем большинстве случаев. Индексы для внешних ключей критически важны для производительности операций, связанных с целостностью данных и соединениями таблиц.

Без индекса на столбце с внешним ключом:

  1. Проверка ограничений (CONSTRAINT) при операциях INSERT/UPDATE в дочерней таблице потребует полного сканирования (FULL SCAN) родительской таблицы для каждого ряда.
  2. Операции DELETE/UPDATE в родительской таблице (особенно с каскадными действиями CASCADE) будут выполняться крайне медленно, так как СУБД не сможет быстро найти зависимые строки в дочерней.
  3. Запросы с JOIN по этому ключу будут неэффективными.

Пример создания (MySQL/PostgreSQL):

-- Индекс часто создается автоматически при объявлении FOREIGN KEY.
-- Но лучше явно убедиться в его наличии.
ALTER TABLE `order_items` 
ADD INDEX `idx_order_id` (`order_id`),
ADD CONSTRAINT `fk_order_items_orders` 
    FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`)
    ON DELETE CASCADE;

Исключения, когда индекс может быть избыточным, крайне редки:

  • Таблица-справочник с очень малым и фиксированным количеством строк (например, statuses с 5 записями).
  • Таблица, в которую только пишут (лог-таблица) и никогда не читают с JOIN или WHERE по этому ключу (маловероятный сценарий).

Правило простое: внешний ключ → индекс.

Ответ 18+ 🔞

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

В 99.9% случаев — ставь, блядь, индекс. Это не обсуждается. Иначе ты сам себе злобный буратино.

Представь, что будет без индекса:

  1. Каждый раз, когда ты вставляешь новую запись в дочернюю таблицу (например, order_items), база данных, чтобы проверить, а есть ли такой order_id в родительской таблице orders, вынуждена делать полный перебор всей таблицы orders. Это, ёпта, FULL SCAN. Если заказов миллион — она будет этот миллион просматривать для каждой новой позиции в заказе. Ебать колотить, какой кошмар.
  2. Хуже того, если ты удаляешь заказ из orders (особенно с каскадом ON DELETE CASCADE), база не знает, где искать связанные строки в order_items. Ей опять придётся сканировать всю дочернюю таблицу целиком, чтобы найти все позиции для удаления. Это просто терпения ноль ебать.
  3. Ну и любые запросы с JOIN по этому полю будут тормозить как мартышлюшка в январской луже. Оптимизатор просто разведёт руками и пойдёт делать тот самый полный сканирующий ёб... прости, поиск.

Как это делается (на примере MySQL/PostgreSQL):

-- Часто индекс создаётся автоматом, когда ты FOREIGN KEY объявляешь.
-- Но надеяться на авось — это путь вникуда. Лучше явно прописать, чтобы спать спокойно.
ALTER TABLE `order_items` 
ADD INDEX `idx_order_id` (`order_id`), -- Вот он, наш спаситель!
ADD CONSTRAINT `fk_order_items_orders` 
    FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`)
    ON DELETE CASCADE;

Исключения? Да они почти мифические!

  • Ну, допустим, у тебя табличка-справочник размером в пять строк, типа statuses. На неё все ссылаются, но она микроскопическая. В этом случае, может, и не нужен индекс — сканировать пять строк не страшно.
  • Или вот ещё фантастический сценарий: есть таблица, в которую только пишут (какой-нибудь лог), и ты никогда-никогда не делаешь по этому ключу SELECT, JOIN или DELETE из родительской таблицы. Ну такое представить сложно, честно говоря.

Вывод простой, как три копейки: видишь внешний ключ — тут же лепи на него индекс. Не усложняй жизнь ни себе, ни базе данных. Иначе потом будешь сам от себя охуевать, глядя на графики медленных запросов.