Ответ
Натуральный (естественный) ключ — это атрибут или набор атрибутов, имеющих бизнес-значение и уникально идентифицирующих запись (например, email, паспортные данные, артикул). Его использование сопряжено с серьезными проблемами:
Основные недостатки:
- Изменчивость (Mutability): Бизнес-данные могут меняться. Смена email пользователя потребует каскадного обновления всех внешних ключей в связанных таблицах, что сложно и рискованно.
- Низкая производительность: Строковые ключи (VARCHAR) обычно длиннее и сравниваются медленнее, чем целочисленные (BIGINT, INT). Это влияет на скорость JOIN, индексацию и размер индексов.
- Сложность обеспечения уникальности: В распределенных системах гарантировать глобальную уникальность значения (например, номера заказа) сложнее, чем использовать автоинкремент базы данных или UUID.
- Семантическая перегрузка: Ключ несет бизнес-смысл, который может со временем измениться или оказаться неверным.
- Сложность составных ключей: Если ключ состоит из нескольких полей, его использование в качестве внешнего ключа становится громоздким и неэффективным.
Рекомендуемый подход: Суррогатный ключ + уникальные ограничения.
-- НЕ РЕКОМЕНДУЕТСЯ: натуральный ключ
CREATE TABLE users (
email VARCHAR(255) PRIMARY KEY, -- Проблема: если email изменится
name VARCHAR(100)
);
-- РЕКОМЕНДУЕТСЯ: суррогатный ключ
CREATE TABLE users (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, -- Быстрый, неизменяемый
email VARCHAR(255) NOT NULL UNIQUE, -- Бизнес-уникальность через ограничение
name VARCHAR(100),
-- Дополнительный индекс для частых поисков по email
INDEX idx_email (email)
);
-- Внешние ключи ссылаются на стабильный `id`
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL, -- Ссылка на суррогатный ключ
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
Реализация в JPA/Hibernate:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // Суррогатный ключ
private Long id;
@Column(unique = true, nullable = false) // Бизнес-уникальность
private String email;
// ... остальные поля
}
Исключения: Натуральные ключи иногда оправданы в справочных таблицах с небольшим, неизменным набором значений (например, country_code CHAR(2) PRIMARY KEY).
Ответ 18+ 🔞
Давай я тебе на пальцах, без всей этой академической хуйни, объясню, почему на натуральных ключах можно влететь так, что мало не покажется.
Представь, что ты завязываешь всю свою жизнь на email пользователя как на главный идентификатор. Вроде логично, email же уникальный, да? А теперь этот пользователь, этот мудак, решает сменить почту. И пошла пиздец-пляска! Все его заказы, комментарии, профили в других сервисах — всё завязано на этот email. Тебе теперь надо бегать по всей базе, как угорелому, и везде это почтовое ведро менять. Один JOIN не там — и ты уже отправил чужой заказ на левый адрес. Ёперный театр!
А производительность? Ну-ка, сравни почту "ivan.pupkin-super-mega-2024@some-fucking-long-domain.com" и просто циферку 123456. Циферка в разы быстрее, меньше места жрёт, индексы на ней летают. А строку ещё и сравнивать почковая система будет посимвольно, как дура. Представь таблицу на миллион записей — там разница будет, как между пешком и на ракете.
И самое пиздатое — в распределёнке. Как ты будешь гарантировать, что номер заказа ORD-2024-05-27-0001 не появится одновременно в двух разных сервисах? Правильно, никак. Будет тебе ORD-2024-05-27-0001 из Москвы и точно такой же ORD-2024-05-27-0001 из Владивостока. А потом они встретятся в общей базе и начнётся драка. Доверия к такой системе — ноль ебать.
Так что же делать, спросишь ты?
Берёшь тупой, безмозглый, но стабильный как швейцарские часы суррогатный ключ. ID, который сам себе генерится и нихуя не значит в бизнес-плане. А поверх него накидываешь уникальное ограничение на те же email или паспорт. Получается идеально: связываешь таблицы по быстрому id, а уникальность бизнес-данных контролирует база.
-- Вот так НЕ НАДО, это путь в ад
CREATE TABLE users (
email VARCHAR(255) PRIMARY KEY, -- Завязались на почту? Готовься к боли.
name VARCHAR(100)
);
-- А вот так — да, блядь, красиво и правильно
CREATE TABLE users (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, -- Быстрый, неизменный, простой
email VARCHAR(255) NOT NULL UNIQUE, -- Уникальность есть, но это не ключ
name VARCHAR(100),
INDEX idx_email (email) -- Ищем по почте тоже быстро
);
-- И все внешние ключи тыкаем в стабильный id
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -- Красота!
);
В коде на Java это выглядит так же элегантно:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // Пусть база сама генерит циферки
private Long id;
@Column(unique = true, nullable = false) // Уникальность почты — под контролем
private String email;
// ... остальная лапша
}
Хотя, конечно, бывают и исключения. Если у тебя таблица стран, где коды 'RU', 'US' никогда не поменяются — можно и их сделать первичным ключом. Но это как ходить по тонкому льду, понимаешь? В девяносто девяти случаях из ста — бери суррогатный ключ и спи спокойно. Волнение ебать — ноль.