4. Ключи и ограничения
🧭 Введение: почему ключи и ограничения важнее «доверия к коду»
Многие начинающие команды сначала пишут логику в приложении и считают, что этого достаточно для контроля данных.
Проблема начинается, когда один сервис пишет в БД напрямую, второй работает через другой API, а третий делает массовую загрузку.
Проблема начинается, когда один сервис пишет в БД напрямую, второй работает через другой API, а третий делает массовую загрузку.
Ключи и ограничения в БД нужны как последняя линия защиты: они не дают сохранить дубли, «битые» ссылки и невалидные значения.
Именно поэтому надёжная схема строится не только на валидации в коде, но и на правилах внутри самой СУБД.
Именно поэтому надёжная схема строится не только на валидации в коде, но и на правилах внутри самой СУБД.
💡 Совет:
Проектируйте ключи и ограничения как контракт данных между всеми сервисами, а не как локальную настройку одного backend-модуля.
✅ Вывод:
Ключи и ограничения — это фундамент целостности данных, без которого система быстро получает скрытые ошибки.
⚠️ Проблема -> решение
Если ограничения не заданы, в БД появляются дубликаты пользователей, заказы на несуществующие товары и статусы вне бизнес-логики.
На старте это может быть незаметно, но при росте продукта такие данные ломают отчёты, расчёты и автоматизацию.
На старте это может быть незаметно, но при росте продукта такие данные ломают отчёты, расчёты и автоматизацию.
Решение — задать ключи (
Тогда СУБД сама блокирует некорректные изменения, а команда тратит меньше времени на «пожарные» исправления.
PRIMARY KEY, FOREIGN KEY) и базовые ограничения (UNIQUE, NOT NULL, CHECK, DEFAULT) на уровне схемы.Тогда СУБД сама блокирует некорректные изменения, а команда тратит меньше времени на «пожарные» исправления.
🟢 Если совсем просто:
Не доверяйте данным «на слово» — закрепляйте правила в БД.
🎯 Как понять, что этап прошёл успешно:
База не даёт вставить дубли и невалидные связи даже при ошибке в коде приложения.
🛠️ Чем помогает и как работает
Ключи и ограничения делают структуру данных предсказуемой: любой сервис пишет в одну и ту же модель с одинаковыми правилами.
Это снижает стоимость поддержки, ускоряет расследование багов и упрощает миграции.
Это снижает стоимость поддержки, ускоряет расследование багов и упрощает миграции.
🟢 Если совсем просто:
Схема с ограничениями сама защищает данные от большинства типовых ошибок.
🎯 Как понять, что этап прошёл успешно:
Некорректные операции ломаются сразу на
INSERT/UPDATE, а не всплывают в проде через неделю.Чем помогает:
- предотвращает дубли ключевых сущностей;
- гарантирует целостность ссылок между таблицами;
- защищает от пустых и нелогичных значений;
- делает данные надёжной основой для аналитики и автоматизации.
Как это работает:
- Шаг 1: определяем, какая колонка (или комбинация колонок) однозначно идентифицирует строку.
- Шаг 2: задаём
PRIMARY KEYдля каждой таблицы. - Шаг 3: описываем связи таблиц через
FOREIGN KEY. - Шаг 4: добавляем
UNIQUEтам, где дубли бизнес-значений недопустимы. - Шаг 5: фиксируем обязательные поля через
NOT NULL. - Шаг 6: ограничиваем допустимые значения через
CHECK. - Шаг 7: задаём безопасные значения по умолчанию через
DEFAULT. - Шаг 8: проверяем сценарии insert/update/delete и действия при удалении (
RESTRICT,CASCADE,SET NULL).
✅ Вывод:
Хорошая схема не просто хранит данные, а активно контролирует их качество.
📚 Ключевые термины (простыми словами)
Перед практикой важно договориться о базовых терминах, чтобы одинаково понимать требования к схеме.
🟢 Если совсем просто:
Это словарь, который нужен для любого ревью SQL-схемы.
🎯 Как понять, что этап прошёл успешно:
Вы можете объяснить каждый термин на примере таблиц своего проекта.
- Primary Key (первичный ключ) — уникальный идентификатор строки, не допускает
NULL. - Foreign Key (внешний ключ) — ссылка на строку в другой таблице.
- Unique Constraint (ограничение уникальности) — запрещает дубли значения/комбинации.
- Not Null Constraint (ограничение обязательности) — запрещает пустое значение.
- Check Constraint (проверка условия) — пропускает только значения, подходящие под правило.
- Default (значение по умолчанию) — автоматически подставляемое значение, если поле не передали.
- Composite Key (составной ключ) — ключ из нескольких колонок.
- Referential Integrity (ссылочная целостность) — гарантия, что ссылка указывает на существующую запись.
✅ Вывод:
Чем точнее команда понимает термины, тем меньше ошибок в проектировании ограничений.
🧱 1. PRIMARY KEY: как однозначно идентифицировать запись
Без первичного ключа таблица теряет управляемость: сложно обновлять конкретную строку и невозможно надёжно строить связи.
PRIMARY KEY — минимальное требование к любой «боевой» сущности.🟢 Если совсем просто:
PK отвечает на вопрос: «Как точно найти эту запись среди всех?»
🎯 Как понять, что этап прошёл успешно:
В каждой таблице есть один явный способ уникально идентифицировать строку.
Назначение:
Обеспечить уникальную идентификацию каждой записи.
Простыми словами:
Это паспорт строки, который не повторяется и не бывает пустым.
Для новичка:
Если у таблицы нет PK, считайте её временной заготовкой, а не полноценной частью модели.
Аналогия:
Как номер паспорта у человека: имена могут совпадать, номер — нет.
Пример:
CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, email TEXT NOT NULL UNIQUE, full_name TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW());🔎 Как это происходит на практике:
- Контекст: команда проектирует таблицу пользователей.
- Действия: выбирает суррогатный ключ
idкак PK и отдельно фиксирует бизнес-уникальность email. - Результат: запись стабильно идентифицируется по
id, а бизнес-правило уникальности тоже соблюдается.
Характеристики:
- PK уникален;
- PK не содержит
NULL; - как правило, в большинстве популярных СУБД для PK автоматически создаётся индекс.
Когда использовать:
Всегда для сущностей, которые участвуют в связях и транзакциях.
✅ Вывод:
PK — базовый якорь целостной схемы.
🔗 2. FOREIGN KEY: защита от «битых» ссылок
Связи между таблицами без FK часто превращаются в «договорённость в коде», которая ломается при обходных вставках и миграциях.
FOREIGN KEY переносит контроль связей в саму СУБД.🟢 Если совсем просто:
FK не даёт ссылаться на запись, которой не существует.
🎯 Как понять, что этап прошёл успешно:
Невозможно создать заказ на несуществующего пользователя или товар.
Назначение:
Гарантировать ссылочную целостность между сущностями.
Простыми словами:
Каждая ссылка должна вести на реально существующую запись.
Для новичка:
Если колонка хранит
*_id другой таблицы, это почти всегда кандидат на FK.Аналогия:
Это как билет в поезд: номер вагона должен существовать в составе.
Пример:
CREATE TABLE orders ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id), status TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW());🔎 Как это происходит на практике:
- Контекст: нужно связать заказ с пользователем.
- Действия:
orders.user_idзакрепляют черезREFERENCES users(id). - Результат: БД не пропускает строки с невалидной ссылкой.
Характеристики:
- защищает от orphan-записей;
- упрощает ревью модели;
- индекс чаще всего нужен на FK-колонке в дочерней таблице (child);
- PK родительской таблицы (parent) обычно уже индексирован.
Когда использовать:
В большинстве OLTP-сценариев и везде, где связь важна для бизнес-логики.
✅ Вывод:
FK превращает связь из «предположения» в технически гарантированное правило.
✅ 3. UNIQUE, NOT NULL, CHECK, DEFAULT: поведенческие правила полей
Ключи отвечают за идентификацию и связи, но этого мало.
Нужны ограничения, которые задают корректные границы значений в бизнес-полях.
Нужны ограничения, которые задают корректные границы значений в бизнес-полях.
🟢 Если совсем просто:
Эти ограничения говорят БД, какие данные допустимы, а какие нет.
🎯 Как понять, что этап прошёл успешно:
БД сама отклоняет пустые, дублирующиеся и нелогичные значения.
Назначение:
Закрепить бизнес-правила на уровне отдельных колонок и их комбинаций.
Простыми словами:
СУБД должна не только хранить, но и «фильтровать мусор».
Для новичка:
Если правило важно для бизнеса, его лучше продублировать в БД, а не держать только в приложении.
Аналогия:
Это как правила на производственной линии: деталь с браком не доходит до финального этапа.
Пример:
CREATE TABLE products ( id BIGSERIAL PRIMARY KEY, sku TEXT NOT NULL UNIQUE, title TEXT NOT NULL, price NUMERIC(10,2) NOT NULL CHECK (price >= 0), status TEXT NOT NULL CHECK (status IN ('draft', 'active', 'archived')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW());🔎 Как это происходит на практике:
- Контекст: товар заводят вручную через админку и автоматически через интеграции.
- Действия: команда задаёт обязательность, уникальность и допустимые значения статуса/цены.
- Результат: некорректные данные блокируются до попадания в рабочие процессы.
Характеристики:
UNIQUEзащищает от дублей;NOT NULLфиксирует обязательность;CHECKограничивает диапазон и справочники статусов;- для статусов часто выбирают
CHECK/ENUM/ справочник +FOREIGN KEY; выбор зависит от частоты изменений и домена; DEFAULTуменьшает количество пропусков при записи.
Когда использовать:
Для всех критичных бизнес-полей с явными правилами.
✅ Вывод:
Эти ограничения снижают число «невидимых» ошибок в данных сильнее, чем любые post-factum проверки.
🧩 4. Составные ключи и выбор стратегии ключа
В практических схемах встречаются как суррогатные ключи (
Главное — выбирать стратегию осознанно, исходя из роли таблицы.
id), так и составные ключи.Главное — выбирать стратегию осознанно, исходя из роли таблицы.
🟢 Если совсем просто:
Для сущностей чаще удобен
id, для таблиц-связок часто уместен составной ключ.🎯 Как понять, что этап прошёл успешно:
Ключ отражает смысл таблицы и не создаёт лишних дублей.
Назначение:
Выбрать формат ключа, который делает модель устойчивой и удобной в сопровождении.
Простыми словами:
Нужен не «модный» ключ, а ключ, который лучше защищает конкретную таблицу.
Для новичка:
Если таблица описывает факт связи N-N, составной PK
(a_id, b_id) часто естественнее отдельного id.Аналогия:
В журнале посещений уникальность может задаваться парой «кто + куда», а не отдельным номером строки.
Пример:
CREATE TABLE order_items ( order_id BIGINT NOT NULL REFERENCES orders(id), product_id BIGINT NOT NULL REFERENCES products(id), qty INT NOT NULL CHECK (qty > 0), unit_price NUMERIC(10,2) NOT NULL CHECK (unit_price >= 0), PRIMARY KEY (order_id, product_id));🔎 Как это происходит на практике:
- Контекст: нужно запретить повтор одной и той же позиции товара в заказе.
- Действия: таблица-связка получает составной PK из двух FK.
- Результат: дубли связки невозможны без дополнительной логики в приложении.
Характеристики:
- хорошо подходит для junction-таблиц;
- упрощает контроль уникальности пары;
- требует аккуратного дизайна зависимых FK в дочерних таблицах.
Когда использовать:
Для таблиц-связок и ситуаций, где бизнес-уникальность естественно задаётся комбинацией колонок.
✅ Вывод:
Стратегия ключа должна следовать доменной логике, а не шаблону «всегда только id».
🛡️ 5. Действия при удалении и обновлении: RESTRICT, CASCADE, SET NULL
Даже правильно заданные FK могут вести к неожиданным эффектам, если не определено поведение при удалении родительской записи.
Поэтому важно выбирать политику каскада сознательно.
Поэтому важно выбирать политику каскада сознательно.
🟢 Если совсем просто:
Нужно заранее решить, что делать с дочерними строками при удалении родителя.
🎯 Как понять, что этап прошёл успешно:
Любое удаление в продакшене ведёт к предсказуемому результату и не нарушает бизнес-логику.
Назначение:
Определить безопасное поведение зависимых данных при изменении/удалении ключевых записей.
Простыми словами:
Система должна «знать», удалять хвосты, блокировать удаление или обнулять ссылку.
Для новичка:
CASCADE удобен, но опасен без чёткого понимания последствий.Аналогия:
Это как удаление папки: можно запретить, можно удалить вместе с файлами, можно оставить файлы отдельно.
Пример:
CREATE TABLE payments ( id BIGSERIAL PRIMARY KEY, order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, amount NUMERIC(10,2) NOT NULL CHECK (amount >= 0), status TEXT NOT NULL CHECK (status IN ('pending', 'paid', 'failed')));🔎 Как это происходит на практике:
- Контекст: заказ удаляют в тестовом и боевом процессах.
- Действия: команда выбирает
CASCADEдля зависимых технических сущностей иRESTRICTдля критичных исторических данных. - Результат: удаление контролируемо и согласовано с бизнес-требованиями.
Характеристики:
RESTRICT— запрет удаления при наличии зависимостей;CASCADE— удаление зависимых строк;SET NULL— отвязка дочерней записи без удаления.- для исторических и аудиторских таблиц часто используют soft delete (
deleted_at) вместо физического удаления; ON UPDATE CASCADEприменяется реже, потому что PK обычно не меняют.
Когда использовать:
При проектировании всех FK, особенно для финансовых и аудиторских сущностей.
✅ Вывод:
Политика удаления — это часть бизнес-логики, а не второстепенная SQL-настройка.
🚀 6. Ограничения в продакшене: как вводить безопасно
Добавление ограничений в живую базу может падать, если старые данные уже нарушают новые правила.
Поэтому важно внедрять ограничения поэтапно, а не «одним ALTER в пятницу вечером».
Поэтому важно внедрять ограничения поэтапно, а не «одним ALTER в пятницу вечером».
🟢 Если совсем просто:
Сначала проверяем и чистим данные, потом включаем жёсткие ограничения.
🎯 Как понять, что этап прошёл успешно:
Миграция проходит без блокирующих ошибок, а приложение не ломается после релиза.
Назначение:
Внедрять ограничения в рабочей БД без потери данных и аварий.
Простыми словами:
Нельзя включать правило, пока данные к нему не готовы.
Для новичка:
Перед добавлением
NOT NULL или UNIQUE всегда делайте проверочный запрос по текущим данным.Аналогия:
Это как ввод нового регламента на заводе: сначала аудит текущего процесса, потом запуск обязательного контроля.
Пример:
-- 1) Находим дубли до UNIQUESELECT email, COUNT(*)FROM usersGROUP BY emailHAVING COUNT(*) > 1; -- 2) После очистки включаем ограничениеALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);Для продакшена в PostgreSQL (поэтапная валидация):
ALTER TABLE orders ADD CONSTRAINT orders_user_id_fk FOREIGN KEY (user_id) REFERENCES users(id) NOT VALID; ALTER TABLE orders VALIDATE CONSTRAINT orders_user_id_fk;Для
UNIQUE в PostgreSQL обычно используют другую стратегию (например, через создание уникального индекса), а NOT VALID чаще применяют для CHECK и FOREIGN KEY.🔎 Как это происходит на практике:
- Контекст: legacy-данные уже содержат нарушения.
- Действия: команда выявляет конфликты, исправляет их и только потом добавляет ограничения.
- Результат: миграция становится повторяемой и безопасной.
Характеристики:
- изменения делаются через миграции;
- сначала аудит данных, потом ограничение;
- в PostgreSQL поэтапная валидация (
NOT VALID->VALIDATE CONSTRAINT) особенно полезна дляCHECKиFOREIGN KEY; - для критичных изменений готовится rollback-план.
Когда использовать:
Всегда при ужесточении схемы в продакшене.
✅ Вывод:
Технически верное ограничение может быть операционно опасным без правильной последовательности внедрения.
⚖️ Сравнение ключевых ограничений
Каждый тип ограничения решает свою задачу, и важно использовать их в связке.
Ниже компактная таблица для быстрого ориентирования.
Ниже компактная таблица для быстрого ориентирования.
🟢 Если совсем просто:
PK определяет запись, FK связывает записи, остальные ограничения задают правила значений.
🎯 Как понять, что этап прошёл успешно:
Для каждого бизнес-правила в схеме есть явный тип ограничения.
| Ограничение | Что контролирует | Типовая ошибка без него |
|---|---|---|
PRIMARY KEY | уникальную идентификацию строки | дубли и неоднозначные обновления |
FOREIGN KEY | ссылочную целостность | ссылки на несуществующие записи |
UNIQUE | недопустимость дубля значения | два одинаковых email/sku |
NOT NULL | обязательность поля | пустые критичные значения |
CHECK | диапазон/список допустимых значений | отрицательная цена, невалидный статус |
DEFAULT | безопасное значение при пропуске | непредсказуемые пустые поля |
✅ Вывод:
Стабильная схема строится не на одном «главном» ограничении, а на их согласованной комбинации.
📌 Must-know факты
Ниже минимум, который нужно помнить для практики и собеседований.
🟢 Если совсем просто:
Это базовый чек-лист качества схемы.
🎯 Как понять, что этап прошёл успешно:
Вы можете пройтись по схеме и подтвердить каждый пункт SQL-примером.
- Каждая основная таблица должна иметь
PRIMARY KEY. - Каждая критичная связь между сущностями должна быть закреплена через
FOREIGN KEY. - Бизнес-уникальность (email, sku, внешний номер) должна иметь
UNIQUE. - Обязательные поля фиксируются через
NOT NULL. - Диапазоны и статусы фиксируются через
CHECK. - Безопасные стартовые значения задаются через
DEFAULT. - Индекс чаще всего нужен на FK-поле дочерней таблицы (child), а PK родителя (parent) обычно уже индексирован.
- Ограничения в проде вводят поэтапно: аудит данных -> очистка -> миграция.
✅ Вывод:
Если хотя бы один must-know блок пропущен, целостность данных быстро деградирует.
🧨 Частые мифы
Ниже мифы, которые чаще всего приводят к «грязной» базе.
🟢 Если совсем просто:
Мифы обычно звучат как «сейчас так быстрее», но потом дают долгие последствия.
🎯 Как понять, что этап прошёл успешно:
Вы можете на реальном кейсе показать, почему миф ведёт к ошибкам.
❌ Миф: Ограничения тормозят разработку, поэтому лучше добавить их потом.
✅ Как правильно: Минимальный набор ограничений нужен сразу, иначе к моменту «потом» данные уже испорчены.
📎 Почему это важно: Исправлять накопленные нарушения сложнее, чем предотвратить их в момент записи.
❌ Миф: Если есть ORM-валидация, ограничения БД не нужны.
✅ Как правильно: ORM-валидация полезна, но БД должна дублировать критичные правила.
📎 Почему это важно: В БД могут писать разные сервисы и скрипты, не проходящие ваш ORM-слой.
❌ Миф:
CASCADE всегда лучший выбор, чтобы «не думать».
✅ Как правильно: Политику удаления выбирают по доменной логике и рискам потери данных.
📎 Почему это важно: Непродуманный CASCADE может удалить критичную историю.❌ Миф:
UNIQUE заменяет PRIMARY KEY.
✅ Как правильно: UNIQUE решает бизнес-уникальность, а PK задаёт основной идентификатор строки.
📎 Почему это важно: Без явного PK усложняются связи, обновления и архитектурная согласованность.❓ Часто спрашивают на собеседованиях
Это типовые вопросы для SQL-интервью по теме целостности данных.
🟢 Если совсем просто:
Проверяют, умеете ли вы защищать данные на уровне схемы, а не только в приложении.
🎯 Как понять, что этап прошёл успешно:
Вы можете ответить с примерами SQL, а не общими словами.
❓ Вопрос: Чем отличается
PRIMARY KEY от UNIQUE?
✅ Ответ: PK — основной идентификатор строки (у таблицы один PK), а UNIQUE — дополнительное правило недопустимости дубля для поля/комбинации.❓ Вопрос: Зачем нужен
FOREIGN KEY, если есть проверки в коде?
✅ Ответ: FK защищает целостность независимо от конкретного сервиса и не позволяет сохранить невалидную ссылку при любом пути записи.❓ Вопрос: Когда использовать составной ключ?
✅ Ответ: Обычно в таблицах-связках, где уникальность определяется парой значений, например
(order_id, product_id).❓ Вопрос: Что делает
CHECK, чего не делает NOT NULL?
✅ Ответ: NOT NULL запрещает пустое значение, а CHECK контролирует допустимый диапазон/список значений.❓ Вопрос: Как безопасно добавить
UNIQUE в продакшене?
✅ Ответ: Сначала найти и устранить дубли, затем добавить ограничение миграцией и проверить ключевые сценарии записи.❓ Вопрос: Когда выбирать
RESTRICT, а когда CASCADE?
✅ Ответ: RESTRICT — когда нельзя терять зависимые данные, CASCADE — когда зависимые записи технически производны и должны удаляться вместе с родителем.❓ Вопрос: Почему индекс на FK-поле часто обязателен?
✅ Ответ: Индекс чаще всего нужен на FK-поле в дочерней таблице (child), потому что без него JOIN и фильтры быстро деградируют; PK родителя обычно уже индексирован.
❓ Вопрос: Можно ли хранить бизнес-ключ как PK?
✅ Ответ: Можно, но чаще выбирают суррогатный PK и отдельно фиксируют бизнес-уникальность через UNIQUE для гибкости изменений.
🚫 Типичные ошибки
Ниже ошибки, которые чаще всего находят на code review SQL-схем.
🟢 Если совсем просто:
Каждая ошибка ниже прямо влияет на качество данных и скорость исправления багов.
🎯 Как понять, что этап прошёл успешно:
Вы проверили схему по каждому пункту и исключили эти риски.
Ошибка 1: таблица без первичного ключа
❌ Неправильно:
Создать таблицу «на время» без PK и оставить её в продакшене.
✅ Правильно:
Сразу задавать PK даже для простых справочников.
Почему:
Без PK растёт риск дублей и усложняются связи.
Ошибка 2: хранить внешние ссылки без FK
❌ Неправильно:
Иметь поле
user_id без REFERENCES users(id).✅ Правильно:
Закреплять ссылку через FK и осознанную политику удаления.
Почему:
Иначе появляются orphan-записи и ломается аналитика.
Ошибка 3: надеяться только на backend-валидацию
❌ Неправильно:
Проверять статусы и диапазоны только в приложении.
✅ Правильно:
Дублировать критичные правила через
CHECK, NOT NULL, UNIQUE.Почему:
В БД могут писать разные потребители вне одного кода.
Ошибка 4: не индексировать FK-поля
❌ Неправильно:
Создать связи, но не добавить индексы под регулярные JOIN.
✅ Правильно:
Индексировать FK и горячие фильтры после моделирования связей.
Почему:
Без индексов нормальная по логике схема может стать медленной в проде.
Ошибка 5: включать ограничения без проверки текущих данных
❌ Неправильно:
Сразу добавлять
UNIQUE/NOT NULL в legacy-таблицу.✅ Правильно:
Сначала аудит и очистка данных, потом ограничение миграцией.
Почему:
Иначе миграция падает и релиз блокируется.
✅ Best Practices
Это практики, которые помогают держать схему устойчивой в долгую.
🟢 Если совсем просто:
Best Practices снижают риск ошибок не только сейчас, но и при росте проекта.
🎯 Как понять, что этап прошёл успешно:
Новая функциональность добавляется без «ручного лечения» данных.
- Давайте каждой таблице явный PK.
- Определяйте бизнес-уникальность отдельными UNIQUE-ограничениями.
- Обязательные поля всегда фиксируйте через NOT NULL.
- Статусы и диапазоны закрепляйте CHECK-правилами.
- Для FK сразу выбирайте поведение при удалении (
RESTRICT/CASCADE/SET NULL). - Индексируйте FK-поля и частые фильтры.
- Внедряйте новые ограничения в продакшене поэтапно.
- Документируйте, почему выбрана конкретная стратегия ключа.
🧾 Заключение
Ключи и ограничения — это не «дополнение» к схеме, а её ядро.
Именно они превращают БД в надёжный источник данных для всех сервисов и процессов.
Именно они превращают БД в надёжный источник данных для всех сервисов и процессов.
Ключевые мысли
- PK отвечает за идентичность строки.
- FK отвечает за корректность связей.
- UNIQUE/NOT NULL/CHECK/DEFAULT отвечают за качество значений.
- Индексы на ключевых полях обеспечивают рабочую производительность.
Если ключи и ограничения продуманы правильно, большая часть ошибок отсекается ещё на уровне записи данных.