Natural keys vs surrogate keys: компромиссы
В прошлом уроке мы построили иерархию ключей и сказали: из нескольких candidate keys проектировщик выбирает один primary key. Теперь — главный вопрос этого выбора. Брать в качестве ключа что-то из реальных данных (email, ИНН, номер паспорта) или сгенерировать искусственный идентификатор, не имеющий смысла? Это разделение на natural keys и surrogate keys, и оно — одно из самых частых решений в проектировании.
Сразу честная рамка: это компромисс, а не «правильный ответ». У каждого подхода есть реальная сила. Но в современном проектировании, особенно в аналитике и data engineering, surrogate keys стали стандартом — и важно понимать не догму, а причины.
Natural key: ключ из реальных данных
Natural key (он же business key) — это ключ, составленный из атрибутов, которые имеют смысл в предметной области и существовали бы, даже если бы базы данных не было.
Примеры natural keys:
emailпользователя.- ИНН организации, номер паспорта человека.
- VIN автомобиля (17-символьный код).
- ISBN книги.
- Номер счёта-фактуры (invoice number).
- Код аэропорта IATA (
SVO,JFK).
Объединяет их то, что это реальные идентификаторы из мира, а не выдумка базы. Бизнес уже использует их в документах, разговорах, других системах.
Сильные стороны natural key:
- Осмысленность. Глядя на
SVO, человек понимает «Шереметьево». Глядя наuser_id = 84217, не понимает ничего. - Естественная защита от дубликатов. Если ключ — это ИНН, то два tuple с одним ИНН физически невозможны — natural key сам по себе несёт уникальность сущности.
- Не нужен лишний JOIN при сверке. Если в двух системах объект опознаётся по одному business key, их данные можно сопоставить напрямую.
Слабые стороны natural key
Теперь — почему natural key проблематичен как primary key. Эти проблемы и привели к доминированию surrogate keys.
1. Natural key меняется. Это главная проблема. Email пользователь меняет. Организация меняет название. Страны переименовывают города. ИНН компании может смениться при реорганизации. А primary key обязан быть стабильным (помните критерий из прошлого урока): если он меняется, изменение нужно протащить через все foreign keys всех таблиц, которые на него ссылаются. Один UPDATE ключа может превратиться в каскад обновлений по десяткам таблиц и миллионам строк.
2. Natural key широкий. VIN — 17 символов. Email — до 254. ISBN — 13 цифр. Составной natural key («код страны + код региона + номер») — ещё больше. Чем шире ключ, тем толще индекс, тем меньше записей помещается на страницу, тем медленнее JOIN. Целочисленный surrogate key — 4-8 байт; этому уроку и физике посвящён последний урок модуля.
3. Natural key не всегда известен сразу. Клиент регистрируется — паспорта ещё нет. Заказ создаётся — номер счёта-фактуры появится позже. А primary key обязан быть NOT NULL с момента вставки строки. Если ключа ещё нет, строку нельзя создать — абсурдная ситуация.
4. Natural key может оказаться не таким уникальным. Реальный мир грязнее, чем кажется. Бывают повторно выданные ИНН, опечатки в документах, исторические VIN с нарушением формата. Ключ, который «по правилам уникален», на практике иногда не уникален — и СУБД на этом сломается.
5. Natural key утекает в чужие системы. Если primary key — это email или номер паспорта, то этот ключ расползается по логам, URL, foreign keys других сервисов. Это и приватность (PII в ключах), и связанность: смена ключа ломает всех.
Surrogate key: искусственный идентификатор
Surrogate key — это искусственный идентификатор, лишённый собственного смысла, сгенерированный системой исключительно для того, чтобы идентифицировать строку.
Типичные формы: автоинкрементное целое (user_id = 1, 2, 3, ...), UUID, hash от business key, Snowflake ID. Стратегии генерации детально разбираются в уроке 4 этого модуля; сейчас важно само понятие.
Ключевое свойство surrogate key — у него нет связи с реальным миром. user_id = 84217 не значит ничего. И именно отсутствие смысла — его сила.
Сильные стороны surrogate key:
- Стабильность. У surrogate key нет причин меняться: он ни на что в реальном мире не завязан. Email пользователь сменит —
user_idостанется. Это полностью снимает первую и главную проблему natural key. - Узость и единообразие. Целочисленный surrogate key — 4 или 8 байт, всегда один столбец. Узкий ключ -> компактный индекс -> быстрый JOIN.
- Доступен сразу. Систему просят сгенерировать ключ в момент вставки строки — он есть всегда, NOT NULL соблюдается тривиально.
- Гарантированная уникальность. Уникальность обеспечивает генератор, а не «надежда, что в реальных данных не будет дубля».
- Не утекает смысл. Surrogate key можно безопасно показывать в URL и логах — он ничего не раскрывает (UUID — особенно, его нельзя перебрать).
Слабые стороны surrogate key:
- Бессмысленность.
user_id = 84217ни о чём не говорит человеку. Для отладки и сверки всё равно нужны осмысленные атрибуты. - Не защищает от дублирования сущности. Surrogate key уникален всегда — но он не мешает завести двух разных пользователей с одним email. Surrogate key гарантирует уникальность СТРОКИ, но не уникальность СУЩНОСТИ.
Последний пункт критичен и часто упускается.
Surrogate key как primary key НЕ отменяет natural key. Если в системе есть business key (email, ИНН), на него почти всегда нужно отдельно поставить UNIQUE-ограничение. Иначе: surrogate key честно даст каждой строке уникальный id, но ничто не помешает создать две строки-дубликата с одинаковым email. Правильный паттерн — surrogate key как PRIMARY KEY плюс natural key как UNIQUE. Surrogate отвечает за идентификацию строки, UNIQUE на natural key — за уникальность сущности.
CREATE TABLE users (
user_id INTEGER PRIMARY KEY, -- surrogate key: идентифицирует строку
email TEXT NOT NULL UNIQUE, -- natural key: гарантирует уникальность сущности
name TEXT NOT NULL
);
-- user_id даёт стабильную узкую идентификацию,
-- UNIQUE(email) не даёт завести двух пользователей с одним email.
-- Нужны ОБА.
Сравнение
| Критерий | Natural key | Surrogate key |
|---|---|---|
| Источник | реальные данные предметной области | сгенерирован системой |
| Смысл для человека | есть | нет |
| Стабильность | может меняться | стабилен |
| Ширина | часто широкий (строки, составные) | узкий (4-8 байт integer) |
| Доступность при вставке | не всегда есть сразу | есть всегда |
| Защита от дубля сущности | есть (встроена в ключ) | нет (нужен отдельный UNIQUE) |
| Утечка смысла в URL/логи | да (риск PII) | нет |
Почему surrogate key стал стандартом — и где natural уместен
В современном проектировании, особенно в OLTP и в аналитических warehouse, surrogate key — выбор по умолчанию для primary key. Причина — совокупность: стабильность (не ломает foreign keys), узость (быстрый JOIN), доступность сразу. В размерном моделировании это вообще обязательно: surrogate key — единственный способ корректно реализовать Slowly Changing Dimensions (отдельный модуль курса), где у одной сущности во времени несколько версий-строк.
Но natural key не исчезает и остаётся уместен:
- Как UNIQUE-ограничение рядом с surrogate primary key — почти всегда.
- Когда natural key гарантированно стабилен и узок — например, ISO-код страны (
RU,US) — двухсимвольный, неизменный; для маленькой справочной таблички он может быть и primary key. - В Data Vault hub-таблицы хранят именно business keys — но и там surrogate hash key используется как технический ключ (модуль про Data Vault).
Главный вывод: понимайте компромисс. Surrogate key решает проблемы изменяемости и ширины — поэтому он стандарт. Natural key несёт смысл и уникальность сущности — поэтому он почти всегда нужен как UNIQUE рядом. Грамотная модель обычно использует оба.
Суррогатные ключи в dbt — generate_surrogate_keyПопробуй сам
Спроектируйте таблицу companies для системы учёта контрагентов. Известно: у компании есть ИНН (в теории уникален, но в реальных данных встречаются дубли и опечатки), название (меняется при ребрендинге), дата регистрации.
- Попробуйте сделать primary key = ИНН (natural key). Перечислите минимум три проблемы, с которыми вы столкнётесь: что произойдёт при опечатке в ИНН, при смене ИНН, при ссылках из других таблиц.
- Перепроектируйте: добавьте surrogate key
company_idкакPRIMARY KEY. Куда теперь деть ИНН? Объясните, почему оставить ИНН вообще без ограничения — ошибка, и почемуUNIQUEна ИНН — правильно (и что делать, если в данных реально есть дубли ИНН). - Напишите итоговый
CREATE TABLE. Создайте таблицуcontracts, ссылающуюся наcompanies. Подумайте: на какой столбецcompaniesдолжен указывать foreign key — на surrogate key или на ИНН? Почему именно на surrogate key? - Сформулируйте одним предложением, за что в вашей схеме отвечает surrogate key, а за что — UNIQUE на natural key.