Как составной первичный ключ родителя влияет на внешние ключи и индексы дочерних таблиц?

Как составной первичный ключ родителя влияет на внешние ключи и индексы дочерних таблиц?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Это не делает составной ключ неправильным, но увеличивает стоимость его распространения по схеме. Если ключ состоит из стабильных и коротких атрибутов, это может быть оправдано; иначе часто выбирают суррогатный ключ, сохраняя отдельное ограничение уникальности бизнес-комбинации.

Исторический контекст

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

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

Постановка проблемы

Пусть аккаунт определяется парой country_code и account_no. Перевод должен ссылаться именно на конкретный аккаунт, поэтому ссылки только на account_no недостаточно: одинаковые номера могут существовать в разных странах.

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

Подробное решение

Внешний ключ должен содержать компоненты в том же логическом порядке и количестве, что и целевой ключ. Соответствие выполняется позиционно: первая колонка дочернего ключа сравнивается с первой колонкой родительского, вторая — со второй.

CREATE TABLE account ( country_code CHAR(2) NOT NULL, account_no VARCHAR(20) NOT NULL, PRIMARY KEY (country_code, account_no) ); CREATE TABLE transfer ( transfer_id INTEGER PRIMARY KEY, src_country CHAR(2) NOT NULL, src_account VARCHAR(20) NOT NULL, FOREIGN KEY (src_country, src_account) REFERENCES account (country_code, account_no) );

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

Составной внешний ключ также усложняет миграции: изменение любой части родительского ключа затрагивает все дочерние строки. Это увеличивает объём обновлений и риск блокировок; поведение нужно явно определить через запрет изменения, каскадное обновление или иной допустимый сценарий.

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

Ситуация из практики

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

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

Выбрали суррогатный идентификатор для внутренних связей и оставили уникальность пары бизнес-атрибутов отдельным ограничением. Это уменьшило размер внешних ключей и упростило миграции, не ослабив правило уникальности счёта.

Что кандидаты часто упускают

  1. Достаточно ли сослаться только на часть составного первичного ключа?

Нет, если эта часть сама не является уникальным ключом родительской таблицы. Внешний ключ должен ссылаться на первичный ключ либо на подходящий уникальный ключ; отдельный номер счёта без кода страны может соответствовать нескольким строкам. Для ссылки только на account_no пришлось бы отдельно гарантировать его глобальную уникальность, что изменило бы модель данных.

  1. Обязательно ли порядок столбцов внешнего ключа должен совпадать с порядком столбцов первичного ключа?

Да, соответствие компонентов задаётся позиционно. Пара (src_country, src_account) должна ссылаться на (country_code, account_no), а не на произвольно переставленную комбинацию. Даже если набор столбцов тот же, неверный порядок меняет смысл сопоставления и обычно делает ограничение некорректным или непригодным для нужной связи.

  1. Всегда ли суррогатный ключ лучше составного?

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