Какой принцип выбора первичного ключа использовать на практике
Что проверяют
Умеете ли вы выбрать ключ, который переживёт изменения бизнеса, и различаете ли естественный и суррогатный ключи. Аналитику следом задают практический вопрос: как вы опишете ключ в модели данных и что делать с идентификаторами из внешних систем.
Как отвечать
Требования к первичному ключу: уникальный, обязательный (NOT NULL), неизменяемый и по возможности компактный, потому что он копируется во все внешние ключи и индексы.
Дальше выбор между двумя видами:
- естественный ключ: значение из предметной области, например ИНН, артикул, email. Понятен людям, но почти всегда когда-нибудь меняется или оказывается неуникальным: email сменили, ИНН ввели с ошибкой, у иностранного контрагента его нет;
- суррогатный ключ: значение без бизнес-смысла, которое генерирует система. Разумный выбор по умолчанию для большинства таблиц.
Варианты суррогатного ключа:
bigintс последовательностью (GENERATED ALWAYS AS IDENTITYв PostgreSQL): компактный и быстрый, но раскрывает количество записей, если попадает в URL;- UUID v4: генерируется где угодно без обращения к базе, удобен при слиянии данных из нескольких систем. Случайный порядок фрагментирует B-tree индекс при частой вставке;
- UUID v7: начинается с метки времени, поэтому новые значения ложатся в конец индекса. Описан в RFC 9562.
Практическое правило: суррогатный ключ первичный, а естественный остаётся в таблице с ограничением UNIQUE, чтобы не было дублей. Для таблиц связи «многие ко многим» уместен составной ключ из двух внешних ключей. В хранилище данных у измерений всегда суррогатный ключ: при SCD второго типа один бизнес-ключ хранит несколько версий записи.
Где спотыкаются
Берут в первичный ключ email, телефон или номер документа. Первая же смена значения требует каскадного обновления всех связанных таблиц и интеграций.
Вводят суррогатный ключ и забывают про уникальность естественного. В итоге один и тот же клиент живёт в базе в трёх экземплярах с разными id.
Строят составной ключ из четырёх-пяти полей там, где он не нужен. Все внешние ключи на эту таблицу тоже становятся составными, а запросы и интеграции заметно усложняются.