Что такое foreign key и зачем он нужен
Что проверяют
Понимаете ли вы, что внешний ключ это ограничение уровня базы, а не описание связи в коде. У питониста вопрос почти всегда уходит в то, что делает ORM и что из этого реально уезжает в схему.
Как отвечать
Внешний ключ требует, чтобы значение в столбце дочерней таблицы существовало в первичном или уникальном ключе родительской либо было пустым. База отвергнет вставку заказа с несуществующим клиентом и не даст удалить клиента, у которого остались заказы.
create table posts (
id bigserial primary key,
user_id bigint not null references users(id) on delete cascade
);Дальше назовите поведение при удалении и обновлении родителя (cascade, restrict, set null, no action), отложенную проверку для циклических связей и главное различие уровней. Связь, объявленная в модели ORM, сама по себе ничего не гарантирует: у SQLAlchemy каскад в relationship отрабатывает в Python, а в схему уходит только ondelete на самом ForeignKey. Django ограничение в базе создаёт, но on_delete исполняет своим кодом, обходя дочерние строки запросами. В SQLite ограничения не проверяются вовсе, пока в соединении не включён соответствующий режим.
Польза формулируется коротко: база остаётся согласованной независимо от того, кто и чем в неё пишет, и осиротевшие строки не появляются после неудачной миграции или ручного запроса.
Где спотыкаются
Уверены, что внешний ключ сам создаёт индекс. В PostgreSQL индексируется только родительская сторона, а столбец-ссылка остаётся без индекса, и удаление родителя начинает перебирать всю дочернюю таблицу.
Забывают про блокировки. Вставка в дочернюю таблицу берёт блокировку на родительскую строку, поэтому массовая загрузка в несколько потоков внезапно упирается в ожидания, которых никто не закладывал.
Отказываются от ограничений ради скорости записи и оставляют целостность на приложение. Достаточно одного скрипта миграции или прямого запроса мимо ORM, и в таблице появляются ссылки в никуда, которые потом вычищают руками.