Back

Основы проектирования реляционных баз данных

Основы проектирования реляционных баз данных

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

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

  • Реляционная база данных хранит данные в таблицах, связанных ключами, а не дублируя информацию между ними
  • Первичные ключи однозначно идентифицируют строки; внешние ключи выражают связи между таблицами
  • Связи «один-ко-многим» используют внешний ключ на стороне «многих», в то время как связи «многие-ко-многим» требуют промежуточной таблицы
  • Нормализация (1НФ, 2НФ, 3НФ) уменьшает избыточность и предотвращает аномалии данных, хотя иногда оправдана намеренная денормализация
  • Ограничения базы данных (PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, CHECK) — это ваша последняя линия защиты целостности данных

Что на самом деле представляет собой реляционная база данных

Реляционная база данных хранит данные в таблицах — структурированных сетках строк и столбцов. Каждая таблица представляет один объект или сущность, например users, orders или products. Каждая строка — это одна запись. Каждый столбец — это один атрибут этой записи.

Мощь реляционной модели заключается в связывании таблиц друг с другом, а не в дублировании данных между ними. Концепция берёт начало из реляционной модели, предложенной Эдгаром Коддом в 1970 году, которая до сих пор формирует теоретическую основу современных SQL-баз данных.

Первичные и внешние ключи: фундамент реляционных схем

Каждой таблице нужен первичный ключ — столбец (или комбинация столбцов), который однозначно идентифицирует каждую строку. Хороший первичный ключ:

  • Уникален для всех строк
  • Никогда не null
  • Стабилен — он не должен изменяться после назначения

Большинство современных SQL-баз данных поддерживают столбцы-идентификаторы (автоинкрементные целые числа), которые автоматически генерируют уникальные идентификаторы. Многие системы также поддерживают ключи на основе UUID, часто генерируемые через встроенные функции базы данных или значения по умолчанию. Оба варианта допустимы в зависимости от контекста. Автоинкрементные целые числа просты и эффективны. UUID лучше подходят для распределённых систем, где несколько источников независимо генерируют записи.

Внешний ключ — это столбец в одной таблице, который ссылается на первичный ключ другой. Так выражаются связи между таблицами. Например, таблица orders может иметь столбец customer_id, который ссылается на customers.id. База данных может обеспечивать эту связь, предотвращая появление «осиротевших» записей. См. документацию PostgreSQL по ограничениям внешних ключей для конкретного примера.

Моделирование связей: «один-ко-многим» и «многие-ко-многим»

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

Связи «многие-ко-многим» — например, студенты, записывающиеся на курсы — требуют промежуточной таблицы (также называемой ассоциативной или связующей таблицей). Вместо попыток хранить несколько значений в одном столбце вы создаёте третью таблицу (enrollments) с внешними ключами, указывающими на students и courses. Это сохраняет схему чистой и удобной для запросов.

Нормализация баз данных: объяснение

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

  • 1НФ (первая нормальная форма): каждая ячейка содержит одно значение — никаких списков через запятую, никаких повторяющихся групп столбцов
  • 2НФ (вторая нормальная форма): каждый неключевой столбец зависит от всего первичного ключа, а не только от его части (это в основном применимо к таблицам с составными первичными ключами)
  • 3НФ (третья нормальная форма): неключевые столбцы зависят только от первичного ключа — не друг от друга

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

Обеспечение целостности с помощью ограничений

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

  • PRIMARY KEY — обеспечивает уникальность и запрет null для идентификатора
  • FOREIGN KEY — гарантирует, что ссылочные строки действительно существуют
  • UNIQUE — предотвращает дублирование значений в столбце (полезно для email, имён пользователей)
  • NOT NULL — предотвращает отсутствие значений там, где поле обязательно
  • CHECK — проверяет, что значение соответствует условию (например, price > 0)

Большинство современных SQL-баз данных также поддерживают вычисляемые столбцы — значения, автоматически вычисляемые из других столбцов — что может уменьшить избыточную логику на уровне приложения. Реализации различаются в разных движках (PostgreSQL, MySQL, SQL Server обрабатывают их по-разному), но концепция широко доступна. Например, PostgreSQL документирует вычисляемые столбцы в своей официальной документации.

Практический пример схемы

CREATE TABLE customers (
  id        INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
  email     VARCHAR(255) NOT NULL UNIQUE,
  name      VARCHAR(100) NOT NULL
);

CREATE TABLE orders (
  id          INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
  customer_id INT NOT NULL REFERENCES customers(id),
  total       NUMERIC(10, 2) CHECK (total >= 0),
  created_at  TIMESTAMP NOT NULL DEFAULT now()
);

Этот пример использует синтаксис SQL в стиле PostgreSQL. Он обеспечивает ссылочную целостность, предотвращает null в email и проверяет, что суммы заказов неотрицательны — всё на уровне базы данных.

Обратите внимание, что GENERATED ALWAYS AS IDENTITY — это стандартный SQL (поддерживается в PostgreSQL 10+, Oracle 12c+ и DB2). Если вы используете MySQL, вместо этого используйте AUTO_INCREMENT. В SQL Server эквивалентом является IDENTITY(1,1). См. документацию PostgreSQL по столбцам-идентификаторам для подробностей.

Заключение

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

Часто задаваемые вопросы

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

Начните с третьей нормальной формы (3НФ) в качестве базового уровня. Если каждый неключевой столбец зависит исключительно от первичного ключа и у вас нет повторяющихся данных в строках, всё в порядке. Если запросы становятся слишком медленными из-за чрезмерного количества объединений, рассмотрите выборочную денормализацию конкретных таблиц с большим количеством чтений, а не полный отказ от нормализации.

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

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

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay