Таблицы и связи

Что это такое

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

Почему это важно

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

Ключевые идеи

Строка — это факт; столбец — атрибут этого факта

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

customers: id, email, created_at
orders:    id, customer_id -> customers.id, total, placed_at

Первичный ключ определяет строку на всю жизнь базы

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

Внешний ключ — это и есть связь, и движок её проверяет

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

Нормализация убирает дублирование фактов, а денормализация — измеренное исключение

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

Ограничения — это документация, которую база проверяет

Обязательность, уникальность, проверки и внешние ключи описывают правила предметной области там, где подчиняться им обязан каждый, кто пишет в базу. Проверка на уровне приложения — всего лишь соглашение, а объявленное ограничение — гарантия. Там, где правило действительно важно, ограничение убирает сам путь в коде, который мог бы про него забыть.

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

Ключи — это ещё и связь с производительностью

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

Практика

Смоделируйте небольшой домен с ограничениями

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

Готово, когда: база отклоняет попытку вставить заказ для несуществующего клиента, а не приложение.

Уберите продублированный факт

Отыщите таблицу, где повторяется атрибут, принадлежащий другой таблице, и верните его туда, где он должен храниться.

Готово, когда: обновление атрибута в одном месте меняет то, что видят все читатели, и ни один запрос не возвращает устаревшую копию.

Частые ошибки

  • Изменяемый естественный ключ как первичный — при изменении значения придётся менять каждую ссылку; исправить: выберите сгенерированный идентификатор и добавьте на естественное значение ограничение уникальности.
  • Отсутствие внешних ключей — строки-сироты копятся, и остановить их нечем; исправить: объявите связь и осознанно выберите поведение при удалении.
  • Хранение денег или дат в слабо типизированных столбцах — арифметика и сравнение ломаются так, что проследить причину трудно; исправить: выбирайте типы, которые выражают значение.
  • Проверка только в приложении — рано или поздно найдётся путь в коде, который её обходит; исправить: выразите правило ограничением.
  • Дублирование данных без записи причины — поздний читатель примет его за ошибку или доверится копии как истине; исправить: объясните причину комментарием и держите одну авторитетную копию.

Что читать дальше