Проектирование реляционных баз данных: курс из 16 лекций

Курс охватывает полный цикл проектирования реляционной базы данных: от сбора требований и моделирования предметной области до нормализации, физического проектирования и эксплуатации. Материал структурирован в 5 глав, каждая включает несколько лекций и практических акцентов.

1. Лекции 1–3: Введение, требования и жизненный цикл БД

Лекции 1–3: Введение, требования и жизненный цикл БД

Зачем проектировать реляционную базу данных

Реляционная база данных (БД) хранит данные в виде таблиц, где:

  • Таблица — это набор строк и столбцов
  • Строка — один объект предметной области (например, заказ)
  • Столбец — одно свойство объекта (например, дата заказа)
  • Проектирование БД — это процесс, который помогает:

  • Сделать данные непротиворечивыми
  • Упростить изменения в будущем
  • Ускорить типовые запросы
  • Снизить риск потери данных
  • Важно отличать:

  • Проектирование БД — что и как мы храним
  • Разработку приложения — как мы показываем и меняем данные
  • > “Bad data costs the U.S. $3 trillion per year.” — оценка стоимости низкого качества данных для экономики. Harvard Business Review

    Базовые понятия, которые нужны с самого начала

    Ниже — термины, которые будут встречаться в курсе постоянно.

    Данные и информация

  • Данные — факты в сыром виде (например, 2026-02-08, 1000, Москва)
  • Информация — данные с контекстом и смыслом (например, “заказ на 1000 ₽ доставить в Москву 8 февраля”)
  • Предметная область

    Предметная область — часть реального мира, которую автоматизирует система (магазин, склад, школа, банк).

    Сущность, атрибут, связь

  • Сущность — тип объекта в предметной области (Клиент, Заказ)
  • Атрибут — свойство сущности (у Клиента есть имя и телефон)
  • Связь — как сущности связаны (Клиент оформляет Заказы)
  • Эти понятия лежат в основе ER-моделирования (Entity–Relationship). Классическое введение в модель связано с работой Питера Чена. Wikipedia: Entity–relationship model

    СУБД и база данных

  • База данных — сами данные + структура хранения
  • СУБД (система управления базами данных) — программный продукт, который хранит, защищает и позволяет запрашивать данные (например, PostgreSQL, MySQL, SQL Server)
  • Транзакция и ACID (на уровне здравого смысла)

    Транзакция — набор изменений, который должен выполниться целиком.

    Принципы ACID обычно формулируют так:

  • Атомарность: либо выполнено всё, либо ничего
  • Согласованность: после изменений данные остаются корректными по правилам
  • Изоляция: параллельные операции не “ломают” друг друга
  • Долговечность: подтверждённые изменения не пропадают
  • На первых лекциях важно запомнить не детали, а идею: БД должна защищать данные от частичных и “сломанных” обновлений.

    Что означает “спроектировать БД”

    Проект БД — это не только таблицы. Обычно вы получаете набор решений и артефактов:

  • Словарь данных (термины и определения): чтобы все одинаково понимали, что такое “клиент”, “заказ”, “оплата”.
  • Модель данных на нескольких уровнях:
  • - Концептуальная: сущности и связи на языке бизнеса - Логическая: таблицы, ключи, ограничения, нормализация - Физическая: типы данных, индексы, партиционирование, настройки хранения
  • Правила качества данных: что обязательно, что уникально, какие диапазоны допустимы.
  • Нефункциональные требования: производительность, безопасность, доступность, резервное копирование.
  • Требования к базе данных

    Требования — это то, что система должна обеспечивать. В проектировании БД важно научиться переводить требования бизнеса в структуру данных и правила.

    Заинтересованные стороны (стейкхолдеры)

    Стейкхолдеры — все, на кого влияет система или кто влияет на требования. Типичные группы:

  • Бизнес-заказчик (владелец продукта)
  • Пользователи (операторы, менеджеры, бухгалтерия)
  • Разработчики приложения
  • Аналитики данных
  • Администратор БД и инфраструктуры
  • Специалист по безопасности и комплаенсу
  • Практический вывод: требования к БД нельзя собрать, поговорив только с одним человеком.

    Типы требований

    #### Функциональные требования

    Описывают, что система должна делать с данными:

  • Хранить клиентов, заказы и оплаты
  • Разрешать отмену заказа
  • Вести историю статусов заказа
  • #### Нефункциональные требования

    Описывают, как хорошо это должно работать:

  • Время ответа на поиск заказа — не более 2 секунд при типовой нагрузке
  • Доступность 99.9%
  • Хранение персональных данных по требованиям закона и политики компании
  • Чтобы формализовать нефункциональные характеристики качества ПО, часто используют модель ISO/IEC 25010. Wikipedia: ISO/IEC 25010

    Ограничения

    Ограничения — рамки, которые нельзя нарушать. Примеры:

  • Используем только PostgreSQL
  • Данные должны храниться в определённом регионе
  • Запрещено хранить полные номера банковских карт
  • Требования к данным (то, что особенно важно для БД)

    Для проектирования БД требования к данным нужно уточнять отдельно:

  • Какие объекты мы храним (сущности)?
  • Какие атрибуты нужны для каждого объекта?
  • Какие атрибуты обязательны?
  • Какие значения допустимы (форматы, диапазоны, справочники)?
  • Что должно быть уникальным?
  • Какие связи между объектами и какова их кратность (один-ко-многим, многие-ко-многим)?
  • Нужна ли история изменений (аудит) и за какой период?
  • Какие операции выполняются чаще всего (чтение, вставка, обновление, удаление)?
  • Как собирать требования: практический набор техник

    Интервью

    Цель интервью — понять процессы и термины бизнеса.

    Хорошие вопросы для старта:

  • Какие задачи человек решает и какие решения принимает на основе данных?
  • Какие документы и поля он заполняет?
  • Какие ошибки случаются сейчас и как их обнаруживают?
  • Какие отчёты нужны и как часто?
  • Анализ документов и текущих источников данных

    Очень часто требования “уже существуют” в виде:

  • Excel-файлов
  • Форм на сайте
  • Бумажных документов
  • Экспортов из старой системы
  • Задача проектировщика — извлечь из них структуру, правила и исключения.

    Наблюдение (shadowing)

    Наблюдение полезно, когда пользователи сами не могут чётко сформулировать процесс (потому что делают его “на автомате”).

    Прототипирование

    Быстрый прототип (например, черновая ER-диаграмма) помогает выявить пропуски: пользователи легче реагируют на “почти готовую” модель, чем на абстрактные вопросы.

    Артефакты требований, которые стоит делать уже на старте

    Глоссарий (словарь терминов)

    Глоссарий снижает риск, когда одно слово означает разное.

    Пример:

    | Термин | Определение | Пример | Комментарий | |---|---|---|---| | Клиент | Человек или компания, оформляющие заказ | “ООО Ромашка” | Может иметь несколько контактных лиц | | Заказ | Намерение купить набор товаров на условиях доставки | Заказ №123 | Может быть отменён | | Оплата | Факт поступления денег за заказ | Платёж 1000 ₽ | Может быть частичной |

    Сценарии использования и CRUD-матрица

    CRUD — это четыре базовые операции над данными:

  • Create (создать)
  • Read (прочитать)
  • Update (обновить)
  • Delete (удалить)
  • CRUD-матрица показывает, какие роли и какие процессы воздействуют на какие сущности.

    Пример (упрощённо):

    | Роль / Процесс | Клиент | Заказ | Оплата | |---|---|---|---| | Оператор колл-центра | C/R/U | C/R/U | R | | Покупатель (через сайт) | C/R/U | C/R/U | C | | Бухгалтерия | R | R | C/R/U |

    Это помогает:

  • Не забыть таблицы, которые нужны “только для отчёта”
  • Найти места, где удаления опасны (например, бухгалтерские данные)
  • Жизненный цикл базы данных

    Жизненный цикл БД — этапы от идеи до эксплуатации и развития. В разных методологиях названия отличаются, но смысл похож.

    !Общая карта этапов жизненного цикла БД и ожидаемых результатов

    Этап идеи и определения границ

    На этом этапе фиксируют:

  • Зачем нужна БД и какую проблему решаем.
  • Что входит в систему, а что нет (границы).
  • Какие внешние системы интегрируются (например, платёжный шлюз, склад, CRM).
  • Результат: короткое описание предметной области и границ.

    Сбор и уточнение требований

    Главная цель — снизить риск “переделок”. Здесь формируют:

  • Глоссарий.
  • Список сущностей и ключевых бизнес-правил.
  • Требования к качеству, безопасности, производительности.
  • Требования к отчётности.
  • Результат: документ требований и набор согласованных определений.

    Концептуальное моделирование

    Концептуальная модель показывает сущности и связи без технических деталей (без типов данных, индексов, конкретной СУБД).

    Примеры вопросов, на которые отвечает концептуальная модель:

  • Может ли заказ существовать без клиента?
  • Может ли у клиента быть несколько адресов?
  • Может ли оплата относиться к нескольким заказам?
  • Результат: ER-диаграмма и словарь данных.

    Логическое проектирование

    На этом этапе концептуальная модель превращается в реляционную:

  • Сущности становятся таблицами.
  • Атрибуты становятся столбцами.
  • Связи оформляются ключами.
  • Определяются ограничения целостности (обязательность, уникальность, справочники).
  • Результат: логическая схема (таблицы, связи, ключи).

    Физическое проектирование

    Физический уровень зависит от СУБД и нагрузки. Здесь решают:

  • Типы данных (например, дата, число, строка).
  • Индексы (чтобы ускорять частые запросы).
  • Партиционирование (если данных очень много и нужны быстрые выборки по диапазонам).
  • Настройки хранения, резервное копирование, репликация.
  • Результат: физическая схема и правила эксплуатации.

    Реализация

    Это создание объектов в СУБД и подготовка к разработке:

  • DDL-скрипты (создание таблиц, ограничений, индексов).
  • Миграции (управляемые изменения схемы во времени).
  • Начальные справочники (если нужны).
  • Результат: БД разворачивается повторяемо (например, на тестовом стенде и в продакшене одинаково).

    Тестирование

    Тестирование БД — не только “запускается ли приложение”. Обычно проверяют:

  • Ограничения целостности (нельзя вставить некорректные данные).
  • Корректность запросов и отчётов.
  • Производительность типовых сценариев.
  • Конкурентный доступ (несколько пользователей одновременно).
  • Результат: подтверждение, что схема выдерживает реальные сценарии.

    Ввод в эксплуатацию и поддержка

    После запуска начинается этап, где БД живёт дольше всего:

  • Мониторинг (нагрузка, медленные запросы, ошибки).
  • Резервное копирование и проверка восстановления.
  • Управление доступами и аудит.
  • Изменения схемы без остановки бизнеса.
  • Результат: стабильная работа и контролируемое развитие.

    Эволюция и вывод из эксплуатации

    Системы меняются: появляются новые поля, отчёты, правила. Важно:

  • Управлять изменениями через миграции.
  • Поддерживать обратную совместимость на переходный период.
  • Архивировать исторические данные по правилам компании и закона.
  • Результат: БД развивается без хаоса и “ломающих” релизов.

    Частые ошибки на старте и как их избегать

  • Начать с таблиц, не договорившись о терминах.
  • Проектировать “под один экран”, игнорируя отчёты и будущие процессы.
  • Не фиксировать бизнес-правила (потом они превращаются в противоречивый код в разных местах).
  • Не обсуждать нефункциональные требования (поздно выясняется, что нужны история, безопасность и высокая доступность).
  • К чему мы придём в следующих лекциях

    Дальше курс будет постепенно переходить от требований к конкретным схемам:

  • Мы научимся строить концептуальные модели (ER)
  • Переводить их в таблицы и связи
  • Обеспечивать целостность данных ограничениями
  • Делать схему удобной для изменений и производительной для запросов
  • 2. Лекции 4–6: Концептуальное моделирование (ER/UML) и бизнес-правила

    Лекции 4–6: Концептуальное моделирование (ER/UML) и бизнес-правила

    Связь с предыдущими лекциями

    В лекциях 1–3 мы разобрали, зачем проектировать реляционную БД, как собирать требования, и какие артефакты полезны на старте: глоссарий, сценарии, CRUD-матрица, границы системы.

    Теперь делаем следующий шаг: превращаем требования в концептуальную модель данных.

    Концептуальная модель отвечает на вопрос: какие данные существуют в предметной области и как они связаны, не привязываясь к конкретной СУБД, типам данных и индексам.

    Что такое концептуальная модель и зачем она нужна

    Концептуальная модель нужна, чтобы:

  • Согласовать понимание предметной области между бизнесом и командой разработки
  • Зафиксировать сущности, их свойства и связи до появления таблиц
  • Рано обнаружить противоречия в требованиях
  • Защититься от проектирования “под один экран” и забытых сценариев
  • Обычно концептуальную модель рисуют как:

  • ER-диаграмму (Entity–Relationship)
  • UML-диаграмму классов (как язык визуализации)
  • Важно: UML в этом месте используется не для ООП-дизайна кода, а как удобная нотация, чтобы показать сущности и связи.

    Базовые элементы ER-моделирования

    ER-модель (модель “сущность–связь”) исторически связана с работой Питера Чена. Подробности и примеры можно посмотреть в Wikipedia: Entity–relationship model.

    Сущность

    Сущность — это тип объектов в предметной области, про которые нужно хранить данные.

    Примеры сущностей для интернет-магазина:

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

    Атрибут

    Атрибут — это характеристика сущности.

    Примеры:

  • У Клиента: имя, телефон, email
  • У Заказа: дата создания, статус, итоговая сумма
  • На концептуальном уровне атрибуты фиксируют смысл, а не технические детали.

  • Хорошо: “телефон клиента”, “дата оплаты”, “валюта платежа”
  • Рано: “varchar(20)”, “timestamp with time zone
  • Связь

    Связь показывает, как сущности взаимодействуют.

    Примеры:

  • Клиент оформляет Заказы
  • Заказ содержит Товары
  • Заказ может быть оплачен Платежами
  • Связь важнее “стрелочки на картинке”: она выражает бизнес-смысл и ограничения.

    Кратность и обязательность связей

    Для каждой связи нужно определить два параметра:

  • Кратность (сколько объектов может быть связано)
  • Обязательность (может ли связь отсутствовать)
  • Типовые варианты кратности

  • Один-ко-многим: один Клиент может иметь много Заказов
  • Один-к-одному: один Заказ связан с одной сущностью “Доставка” (в некоторых моделях)
  • Многие-ко-многим: один Заказ содержит много Товаров, и один Товар встречается во многих Заказах
  • Как задавать обязательность

    Обязательность обычно формулируют простыми вопросами:

  • Может ли Заказ существовать без Клиента?
  • Может ли Платёж существовать без Заказа?
  • Может ли Клиент существовать без Заказов?
  • Пример типичных ответов:

  • Клиент может существовать без заказов: да (например, зарегистрировался)
  • Заказ без клиента: обычно нет (если не допускаем “гостевой заказ”)
  • Платёж без заказа: нет
  • Эти ответы превращаются в ограничения целостности на логическом уровне, но их источник — именно концептуальный.

    !Пример концептуальной ER-диаграммы интернет-магазина с кратностями

    Как моделировать связь “многие-ко-многим”

    Связь “многие-ко-многим” почти всегда означает, что между сущностями есть отдельный бизнес-объект.

    Пример:

  • Заказ и Товар связаны “многие-ко-многим”
  • Но бизнесу важны количество и цена на момент покупки
  • Значит появляется сущность, которую часто называют:

  • ПозицияЗаказа
  • OrderItem
  • СтрокаЗаказа
  • Её смысл: “конкретный товар в конкретном заказе”.

    Концептуальный сигнал, что нужна такая сущность:

  • У связи есть собственные атрибуты (количество, скидка, цена)
  • Нужна история (цена в прошлом)
  • Нужна уникальность на уровне пары (Заказ, Товар)
  • Идентификаторы на концептуальном уровне

    В реляционной БД будут первичные ключи, но на концептуальном уровне достаточно договориться об идентификаторе сущности.

    Два основных подхода:

  • Естественный идентификатор: номер паспорта, VIN, ИНН (если он обязателен и стабилен)
  • Суррогатный идентификатор: технический id, не несущий бизнес-смысла
  • Практическое правило для концептуальной модели:

  • Если бизнес гарантирует уникальность и неизменяемость поля — можно считать его идентификатором
  • Если есть сомнения — фиксируйте суррогатный id, а уникальность “естественных” полей оформляйте как бизнес-правило
  • UML-диаграмма классов как нотация для концептуальной модели

    UML (Unified Modeling Language) — стандартный язык моделирования. Для обзорного описания нотации можно использовать Wikipedia: UML class diagram.

    Как читать UML-диаграмму классов в контексте данных:

  • Класс = сущность
  • Атрибуты класса = атрибуты сущности
  • Ассоциация = связь
  • Кратность показывается как 0..*, 1..1 и т.д.
  • Важно не перепутать:

  • В разработке ПО UML-класс может иметь методы
  • В концептуальной модели данных методы не нужны: мы моделируем данные и правила
  • !Пример UML-диаграммы классов как концептуальной модели данных

    Что такое бизнес-правила и почему их нельзя оставлять “в голове”

    Бизнес-правило — это ограничение или правило, которое делает данные корректными с точки зрения предметной области.

    Если бизнес-правила не зафиксировать, они расползутся:

  • Частично будут в коде приложения
  • Частично в головах сотрудников
  • Частично в ручных инструкциях
  • В итоге данные станут противоречивыми, а исправления — дорогими.

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

    #### Правила обязательности

    Примеры:

  • У заказа обязательно должен быть клиент
  • У платежа обязательно должна быть дата
  • #### Правила уникальности

    Примеры:

  • Email клиента уникален среди активных клиентов
  • Номер заказа уникален в пределах года
  • #### Правила домена (допустимых значений)

    Примеры:

  • Статус заказа только из списка: “Новый”, “Оплачен”, “Отменён”, “Отгружен”
  • Валюта платежа только из ISO-кодов (например, RUB, USD)
  • Если набор значений ограничен и управляем, обычно выделяют отдельную сущность-справочник.

    #### Правила согласованности между сущностями

    Примеры:

  • Сумма платежей по заказу не должна превышать сумму заказа (или должна соответствовать правилам переплаты)
  • Нельзя удалить заказ, если по нему есть платежи (или удаление возможно только через “отмену”)
  • #### Временные правила

    Примеры:

  • Дата доставки не может быть раньше даты создания заказа
  • Возврат возможен не позднее 14 дней после получения
  • На концептуальном уровне достаточно зафиксировать правило словами. Реализация (ограничения, триггеры, проверки в приложении) появится позже.

    #### Правила переходов состояний

    Если у сущности есть статус, почти всегда есть правила переходов.

    Пример:

  • “Оплачен” может перейти в “Отгружен”
  • “Отменён” не может перейти обратно в “Новый”
  • Эти правила важно согласовать заранее, иначе “статус” превращается в произвольный текст.

    !Пример фиксации бизнес-правил через диаграмму состояний

    Как документировать бизнес-правила рядом с моделью

    Один рисунок не заменяет текст. Хорошая практика — завести таблицу бизнес-правил.

    Пример структуры:

    | Код правила | Описание | Где применяется | Комментарий | |---|---|---|---| | BR-001 | Заказ должен ссылаться на клиента | Заказ | Исключение: гостевой заказ, если разрешим позже | | BR-002 | Статус заказа только из списка | Заказ.status | Список управляется справочником | | BR-003 | Позиция заказа должна ссылаться на товар | ПозицияЗаказа | Удаление товара запрещено, если он в заказах | | BR-004 | Email клиента уникален среди активных | Клиент.email | Не распространяется на архивных |

    В следующих лекциях эти правила станут:

  • NOT NULL
  • UNIQUE
  • внешними ключами
  • справочниками
  • ограничениями и проверками
  • Но начинать нужно со смысла.

    Пошаговый процесс построения концептуальной модели

    Ниже — практический процесс, который хорошо “стыкуется” с артефактами из лекций 1–3.

    Уточнить границы и термины

  • Проверьте, что глоссарий согласован
  • Убедитесь, что одинаковые слова не означают разное
  • Выписать кандидатов в сущности

    Источники:

  • Документы (заказ, накладная, возврат)
  • Экраны и формы ввода
  • Отчёты
  • Определить связи и их смысл

  • Кто кого создаёт?
  • Кто без кого не существует?
  • Где связь “многие-ко-многим”?
  • Проставить кратности и обязательность

  • Задавайте вопросы “может ли существовать без…?”
  • Проверяйте редкие сценарии (отмена, возврат, частичная оплата)
  • Добавить атрибуты сущностей

  • Оставляйте только то, что реально нужно хранить
  • Отделяйте “хочу вывести в интерфейсе” от “нужно хранить”
  • Зафиксировать бизнес-правила

  • Обязательность
  • Уникальность
  • Допустимые значения
  • Ограничения по времени
  • Переходы статусов
  • Провалидировать модель на сценариях

    Используйте сценарии и CRUD-матрицу:

  • Есть ли сущности, которые никто не создаёт?
  • Есть ли сущности, которые нельзя прочитать или связать в отчёте?
  • Есть ли “опасные удаления”, которые нарушат правила?
  • Хорошая практика — пройти 5–10 реальных историй:

  • “Клиент оформил заказ и оплатил частично”
  • “Заказ отменили до оплаты”
  • “Товар сняли с продажи, но он есть в старых заказах”
  • Частые ошибки в концептуальном моделировании

  • Смешивать концептуальный и логический уровни: типы данных и индексы появляются слишком рано
  • Рисовать связи без формулировки смысла: “линия есть, правила нет”
  • Пропускать сущности, которые “живут” между объектами: позиции заказа, записи истории статусов
  • Пытаться хранить “всё одним полем JSON”, потому что требования неясны
  • Не фиксировать правила статусов: потом невозможно объяснить, какие значения допустимы
  • Что будет дальше в курсе

    В следующих лекциях мы начнём переводить концептуальную модель в реляционную:

  • Таблицы, первичные ключи и внешние ключи
  • Связи один-ко-многим и многие-ко-многим на уровне таблиц
  • Ограничения целостности и нормализация
  • Концептуальная модель и бизнес-правила будут служить “контрактом”: если логическая схема противоречит концептуальной, значит мы теряем смысл данных или создаём риск ошибок.

    3. Лекции 7–10: Логическая модель, ключи, связи и нормализация

    Логическая модель, ключи, связи и нормализация

    Связь с предыдущими лекциями

    В лекциях 1–3 мы научились собирать требования, фиксировать термины и понимать жизненный цикл БД. В лекциях 4–6 мы построили концептуальную модель (ER/UML) и выписали бизнес-правила.

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

  • На концептуальном уровне мы говорили: Сущность Клиент связана с Заказом, у связи есть кратность и обязательность.
  • На логическом уровне мы обязаны сказать конкретно: какие таблицы будут, какие поля, какие ключи, какие ограничения и как обеспечить целостность данных.
  • Что такое логическая модель реляционной БД

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

    Логическая модель отвечает на вопросы:

  • Какие таблицы существуют и что означает каждая.
  • Какие столбцы есть в таблицах и какие правила на них действуют.
  • Как таблицы связаны.
  • Какие значения допустимы и как БД не позволит записать “плохие” данные.
  • Реляционная модель исторически связана с работами Эдгара Кодда. Если нужно справочно: Relational model.

    Таблица, строка, столбец: перевод с языка бизнеса

    Перед тем как говорить о ключах и нормализации, важно одинаково понимать базовые элементы.

  • Таблица — множество строк одного типа.
  • Строка — один факт об одном объекте.
  • Столбец — одно свойство.
  • Практическое правило:

  • Если вы не можете внятно сказать, какой факт хранит строка таблицы, таблица почти наверняка спроектирована плохо.
  • Пример:

  • Таблица orders хранит факты “какие заказы существуют”.
  • Одна строка в orders — один заказ.
  • Поле created_at — дата и время создания заказа.
  • Ключи: как БД отличает одну строку от другой

    Первичный ключ

    Первичный ключ — это столбец или набор столбцов, которые однозначно идентифицируют строку таблицы.

    Свойства первичного ключа:

  • Уникален: не бывает двух строк с одинаковым ключом.
  • Обязателен: у каждой строки ключ должен быть задан.
  • Примеры:

  • customer_id в таблице клиентов.
  • Составной ключ (order_id, product_id) в таблице позиций заказа (если так решили).
  • Кандидатный ключ и альтернативный ключ

    Иногда строку можно однозначно определить разными способами.

  • Кандидатный ключ — любой набор полей, который мог бы быть первичным ключом.
  • Альтернативный ключ — кандидатный ключ, который не выбрали как первичный.
  • Пример для клиента:

  • customer_id — суррогатный первичный ключ.
  • email — альтернативный ключ, если по бизнес-правилу он уникален.
  • Естественный и суррогатный ключ

  • Естественный ключ имеет бизнес-смысл (например, VIN автомобиля).
  • Суррогатный ключ — технический идентификатор (например, id), не несущий смысла.
  • Когда чаще выбирают суррогатный ключ:

  • Естественный “уникальный” атрибут может меняться.
  • Уникальность зависит от условий (например, “уникален среди активных клиентов”).
  • Естественный ключ слишком длинный или составной.
  • Когда естественный ключ уместен:

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

    Ограничения целостности: как встроить бизнес-правила в БД

    Ограничения целостности — это правила, которые СУБД проверяет автоматически.

    Основные виды:

  • NOT NULL: поле обязательно.
  • UNIQUE: значение уникально.
  • CHECK: проверка допустимого диапазона или формата (в пределах того, что разумно проверять на уровне БД).
  • FOREIGN KEY: внешний ключ, который обеспечивает ссылочную целостность.
  • Справочно по ограничениям (на примере PostgreSQL): PostgreSQL Constraints.

    Связи между таблицами: как реализуется кратность из концептуальной модели

    В концептуальной модели мы говорили “один-ко-многим” и “многие-ко-многим”. В логической модели это превращается в конкретные конструкции.

    !Памятка: как переводить кратности ER-модели в таблицы

    Связь один-ко-многим

    Пример из интернет-магазина:

  • Один клиент может иметь много заказов.
  • Каждый заказ принадлежит одному клиенту.
  • Реализация:

  • В таблице orders появляется столбец customer_id.
  • customer_id — внешний ключ на customers(customer_id).
  • Если по правилу “заказ не может быть без клиента”, то orders.customer_id дополнительно делается NOT NULL.

    Связь один-к-одному

    Связь один-к-одному возникает, когда:

  • Это действительно разные сущности.
  • Но каждому объекту A соответствует не больше одного объекта B.
  • Типовой пример: orders и order_delivery_details, если детали доставки появляются не всегда или сильно отличаются по набору полей.

    Реализация:

  • В таблице B хранится внешний ключ на A.
  • На этот внешний ключ ставится UNIQUE, чтобы “не размножить” строки B для одного A.
  • Важно: многие связи, которые кажутся 1:1, на практике превращаются в 1:N из-за истории или версий (например, “адрес доставки менялся”). Это нужно обсуждать на уровне бизнес-правил.

    Связь многие-ко-многим

    Классический пример:

  • Один заказ содержит много товаров.
  • Один товар встречается во многих заказах.
  • В реляционной модели связь многие-ко-многим напрямую не хранят. Почти всегда добавляют таблицу-связку, которая становится отдельной сущностью.

    Пример таблицы-связки:

  • order_items(order_id, product_id, qty, price_at_moment)
  • Практически полезные ограничения для такой таблицы:

  • FOREIGN KEY (order_id)orders(order_id)
  • FOREIGN KEY (product_id)products(product_id)
  • UNIQUE(order_id, product_id) если в заказе нельзя иметь один и тот же товар двумя строками
  • Если бизнес допускает несколько строк одного товара (например, разные скидки или разные сроки поставки), то UNIQUE(order_id, product_id) не подходит: нужен отдельный идентификатор строки заказа.

    Внешние ключи и правила удаления/обновления

    Внешний ключ гарантирует, что ссылка указывает на существующую строку.

    Пример: если в orders.customer_id = 10, то в customers обязана существовать строка с customer_id = 10.

    Но всегда нужно договориться, что происходит при удалении “родительской” строки.

    Типовые варианты поведения:

  • RESTRICT или NO ACTION: запретить удаление, если есть зависимые строки.
  • CASCADE: удалить зависимые строки автоматически.
  • SET NULL: обнулить ссылку.
  • Выбор зависит от смысла данных:

  • Если заказы — юридически или финансово значимые данные, CASCADE почти всегда опасен.
  • Если связь техническая и данные действительно “владение” (например, временные токены пользователя), CASCADE может быть уместен.
  • Практическое правило: режим удаления — это бизнес-правило, а не “настройка по умолчанию”.

    Как переводить концептуальные сущности в таблицы

    Ниже — рабочий алгоритм, который опирается на ER-модель из лекций 4–6.

  • Каждую сущность превратить в таблицу.
  • Для каждой таблицы выбрать первичный ключ.
  • Атрибуты сущности превратить в столбцы.
  • Каждую связь 1:N реализовать внешним ключом на стороне N.
  • Каждую связь M:N реализовать отдельной таблицей.
  • Для доменов “строго из списка” решить:
  • - либо CHECK (если список короткий и редко меняется), - либо справочник-таблица + внешний ключ (если список управляемый, расширяемый, нужен перевод названий, порядок, признаки активности).
  • Все бизнес-правила из таблицы правил попытаться отразить ограничениями:
  • - обязательность → NOT NULL - уникальность → UNIQUE - допустимые значения → CHECK или справочник - согласованность между таблицами → внешние ключи и иногда дополнительные механизмы (об этом подробнее будет в следующих лекциях)

    Нормализация: зачем она нужна и от чего защищает

    Нормализация — это набор правил, которые помогают сделать схему:

  • без дублирования “истины”
  • без противоречий при обновлениях
  • удобной для расширения
  • Классическая справка: Database normalization.

    Три типовые проблемы без нормализации

    Эти проблемы часто называют аномалиями.

  • Аномалия обновления
  • - Одно и то же значение хранится в нескольких местах. - Обновили в одном месте, забыли в другом.

  • Аномалия вставки
  • - Нельзя сохранить факт A, не создавая искусственно факт B. - Например, нельзя завести товар, пока нет заказа.

  • Аномалия удаления
  • - Удаляя строку “про одно”, вы случайно теряете факт “про другое”. - Например, удалили последний заказ клиента и потеряли его контактные данные.

    Нормализация — способ проектировать так, чтобы эти ситуации не возникали.

    Первая нормальная форма

    Первая нормальная форма (1НФ) в практическом смысле означает:

  • В каждой ячейке таблицы хранится одно значение.
  • Нет “списков в одном поле” вроде "товар1, товар2, товар3".
  • Плохой пример:

  • orders.products = "клавиатура, мышь"
  • Хороший вариант:

  • orders хранит заказ как факт.
  • order_items хранит строки заказа (позиции).
  • Почему это важно:

  • Иначе невозможно корректно индексировать, проверять целостность и писать предсказуемые запросы.
  • Вторая нормальная форма

    Вторая нормальная форма (2НФ) становится важной, когда у таблицы составной первичный ключ.

    Идея простыми словами:

  • Если ключ составной, то каждый неключевой столбец должен зависеть от всего ключа, а не от его части.
  • Пример ситуации:

  • Таблица order_items(order_id, product_id, product_name, qty)
  • Если product_name зависит только от product_id, то это нарушение: название товара нужно хранить в products, а в order_items оставлять только ссылку на товар.
  • Практический вывод:

  • Справочные свойства товара хранятся в products.
  • В order_items остаются свойства факта “товар в заказе”, например qty, price_at_moment, discount.
  • Третья нормальная форма

    Третья нормальная форма (3НФ) убирает “зависимости через другое поле” внутри одной таблицы.

    Идея простыми словами:

  • Нельзя хранить в таблице A такие поля, которые на самом деле определяются другим неключевым полем.
  • Пример:

  • В customers храним city_name и city_postal_code.
  • Если почтовый код определяется городом, то при смене правил или исправлении ошибок придётся обновлять много строк.
  • Обычно решение такое:

  • Выделить справочник cities(city_id, city_name, postal_code).
  • В customers хранить city_id.
  • Важная оговорка:

  • Нормализация — не про “всё в справочники”, а про то, где находится источник истины.
  • !Иллюстрация: как 3НФ убирает дублирование и противоречия

    Функциональная зависимость: объяснение без формул

    В описаниях нормальных форм часто встречается термин функциональная зависимость.

    Простое объяснение:

  • Поле B функционально зависит от поля A, если для каждого значения A существует ровно одно соответствующее значение B.
  • Примеры:

  • product_idproduct_name (у одного товара одно название в справочнике)
  • country_codecountry_name
  • Зачем это понимать:

  • Это помогает отличать “свойства объекта” от “свойств факта”.
  • И правильно раскладывать данные по таблицам.
  • Нормализация и производительность: когда допускают денормализацию

    Денормализация — это сознательное нарушение строгой нормализации ради скорости чтения или удобства отчётов.

    Когда она может быть оправдана:

  • Есть критичные отчёты, и нормальная схема приводит к слишком тяжёлым соединениям.
  • Есть витрины данных или агрегаты, которые можно пересчитывать.
  • Нужна историзация “как было тогда”, и проще хранить часть данных копией (например, адрес доставки на момент заказа).
  • Как денормализовать безопаснее:

  • Делать это точечно и документировать.
  • Явно решать, где “источник истины”, а где “копия для чтения”.
  • Ставить ограничения и/или процессы обновления, чтобы копии не расходились.
  • Мини-пример логической схемы по концептуальной модели интернет-магазина

    Ниже — упрощённая логическая схема, чтобы связать всё вместе.

    Таблицы и ключи

    | Таблица | Смысл строки | Первичный ключ | Важные ограничения | |---|---|---|---| | customers | один клиент | customer_id | email может быть UNIQUE | | orders | один заказ | order_id | customer_id NOT NULL, FOREIGN KEY | | products | один товар | product_id | sku UNIQUE (если есть артикул) | | order_items | один товар в одном заказе | order_item_id или (order_id, product_id) | внешние ключи на orders и products | | order_statuses | один статус из справочника | status_code | список управляемых статусов |

    Пример DDL-скелета (без привязки к конкретной СУБД)

    Что важно в этом примере:

  • Связь 1:N customersorders реализована FK в orders.
  • Связь M:N ordersproducts реализована таблицей order_items.
  • Поле price_at_moment специально хранится в order_items: это свойство факта покупки, а не свойство товара.
  • Типовые ошибки на логическом уровне

  • Смешивать свойства объекта и свойства факта
  • - Например, хранить “текущую цену товара” в строке заказа вместо “цену на момент покупки”.

  • Делать M:N без таблицы-связки
  • - Это приводит к спискам в одном поле и нарушению 1НФ.

  • Не фиксировать обязательность связей
  • - В ER-модели сказали “заказ всегда с клиентом”, но забыли NOT NULL и получили “пустые” заказы.

  • Игнорировать правила удаления
  • - Случайный CASCADE может стереть историю продаж.

  • Увлекаться справочниками без необходимости
  • - Нормализация важна, но справочник ради справочника усложняет систему.

    Что будет дальше в курсе

    После логической схемы обычно переходят к тому, как сделать её рабочей в реальной СУБД:

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

    4. Лекции 11–13: Физическое проектирование, индексы, планы запросов, тюнинг

    Лекции 11–13: Физическое проектирование, индексы, планы запросов, тюнинг

    Связь с предыдущими лекциями

    В лекциях 1–3 мы научились собирать требования и понимать жизненный цикл БД. В лекциях 4–6 построили концептуальную модель и зафиксировали бизнес-правила. В лекциях 7–10 перевели модель в логическую реляционную схему: таблицы, ключи, связи, ограничения и нормализацию.

    Теперь мы переходим к физическому проектированию и производительности.

  • На логическом уровне мы говорили что хранить и какие правила должны быть истинны.
  • На физическом уровне мы решаем как именно хранить и как быстро получать данные в выбранной СУБД.
  • Важно: физические решения всегда зависят от реальных запросов и нагрузки. Поэтому в этих лекциях мы будем постоянно связывать:

  • типовые запросы
  • индексы
  • планы выполнения
  • сбор статистики
  • измерения и тюнинг
  • Что такое физическое проектирование

    Физическое проектирование — это набор решений, которые определяют реальную работу БД:

  • выбор типов данных и их влияние на размер и сравнение
  • индексы и их состав
  • разбиение данных (партиционирование)
  • подходы к истории и архиву
  • настройки хранения и обслуживания (например, обновление статистики)
  • Физический уровень должен поддерживать бизнес-требования из первых лекций:

  • производительность
  • надежность
  • безопасность
  • стоимость владения
  • Типы данных как фундамент производительности

    Тип данных влияет на:

  • размер строк и страниц хранения
  • скорость сравнений и сортировок
  • возможность использовать индексы эффективно
  • Практические правила выбора типов

  • Храните числа числами, даты датами, а не строками
  • - плохо: "2026-02-08" в текстовом поле - хорошо: DATE или TIMESTAMP
  • Выбирайте минимально достаточный тип
  • - плохо: все идентификаторы как “огромные строки” - хорошо: числовые BIGINT или UUID, если он реально нужен
  • Будьте осторожны с “универсальным текстом”
  • - текстовые поля удобны, но часто приводят к ошибкам в валидации и тяжелым сравнениям

    Если используете PostgreSQL, справочник по типам данных: PostgreSQL: Data Types

    Типичная ошибка

    Хранить “значение + единицы” в одном поле.

  • плохо: "1000 RUB"
  • хорошо: отдельные поля amount и currency_code, где currency_code связан со справочником
  • Это решение начинается на логическом уровне, но физически оно влияет на индексы, агрегации и сортировки.

    Индексы: что это и зачем они нужны

    Индекс — это структура данных, которая помогает находить строки быстрее, чем полный просмотр таблицы.

    Простая интуиция:

  • без индекса БД часто вынуждена читать много лишних строк
  • с индексом БД может “перепрыгнуть” сразу к нужным
  • В PostgreSQL обзор по индексам: PostgreSQL: Indexes

    !Интуитивная иллюстрация, почему индекс ускоряет поиск

    Цена индекса

    Индексы ускоряют чтение, но имеют стоимость:

  • занимают место
  • замедляют вставки, обновления и удаления, потому что индекс тоже нужно обновлять
  • требуют обслуживания (вакуум, статистика, иногда перестроение)
  • Физическое проектирование — это баланс.

    Основные виды индексов и когда они нужны

    Ниже — не полный список всех вариантов, а минимальный набор, который чаще всего нужен в прикладных системах.

    Индекс на первичный ключ и внешние ключи

  • первичный ключ почти всегда индексируется автоматически
  • внешний ключ не всегда автоматически создает индекс, но он часто нужен
  • Почему индекс на внешний ключ важен:

  • ускоряет соединения (JOIN)
  • ускоряет проверки целостности при удалении или обновлении в родительской таблице
  • Практический пример:

  • есть orders(customer_id) как внешний ключ на customers(customer_id)
  • если вы часто ищете заказы клиента или соединяете клиентов с заказами, индекс на orders(customer_id) почти всегда оправдан
  • Составной индекс

    Составной индекс строится по нескольким полям, например (customer_id, created_at).

    Его создают, когда запросы фильтруют или сортируют по комбинации.

    Ключевое правило порядка полей:

  • индекс эффективен “слева направо”
  • Пример:

  • индекс (customer_id, created_at) хорошо работает для условий:
  • - WHERE customer_id = ... - WHERE customer_id = ... AND created_at >= ... - ORDER BY created_at внутри конкретного клиента
  • но он хуже подходит для:
  • - WHERE created_at >= ... без customer_id

    Частичный индекс

    Частичный индекс хранит только часть строк по условию.

    Когда полезен:

  • таблица большая
  • часто ищете “активные” или “неархивные” строки
  • Пример идеи:

  • у orders есть признак is_deleted = false или статус “активен”
  • большинство запросов работает только с активными
  • частичный индекс становится меньше и быстрее
  • В PostgreSQL: PostgreSQL: Partial Indexes

    Индекс по выражению

    Индекс по выражению строится не по самому полю, а по результату функции или выражения.

    Когда полезен:

  • вы фильтруете по преобразованному значению
  • Типичный пример:

  • поиск без учета регистра (в зависимости от требований)
  • В PostgreSQL: PostgreSQL: Indexes on Expressions

    Покрывающий индекс

    Идея покрывающего индекса:

  • запрос может прочитать нужные столбцы прямо из индекса и реже обращаться к таблице
  • Это особенно полезно для “узких” запросов, которые выбирают несколько полей и имеют строгий фильтр.

    В PostgreSQL, начиная с версии 11, это поддерживается через INCLUDE: PostgreSQL: Index-Only Scans and Covering Indexes

    Почему индекс иногда не используется

    Частый вопрос: “Мы создали индекс, почему запрос всё равно медленный?”

    Типовые причины:

  • условие возвращает слишком много строк
  • - если выбирается большая часть таблицы, полный просмотр может быть дешевле
  • несовпадение условия с тем, как устроен индекс
  • - например, функция над полем в WHERE без индекса по выражению
  • устаревшая статистика
  • - планировщик неверно оценивает, сколько строк вернется
  • тип данных и сравнение
  • - сравнение строки и числа, неявные преобразования, разные колляции

    Планы запросов: как БД решает, что делать

    Когда вы отправляете SQL-запрос, СУБД обычно проходит этапы:

  • разбирает SQL (парсинг)
  • проверяет права и корректность
  • строит несколько вариантов выполнения
  • выбирает план с минимальной оценочной стоимостью
  • выполняет
  • План запроса — это дерево операций: чтение таблиц, использование индексов, соединения, сортировки, агрегации.

    В PostgreSQL команда для просмотра плана: PostgreSQL: EXPLAIN

    !Схема-дерево, показывающая, что план запроса состоит из операций

    Как читать EXPLAIN на базовом уровне

    В выводе EXPLAIN важно различать:

  • оценки планировщика
  • фактические значения при выполнении
  • Поэтому для диагностики часто используют:

    Что это дает:

  • ANALYZE показывает фактическое время и фактическое количество строк
  • BUFFERS показывает работу с памятью и чтение страниц
  • Если фактические строки сильно отличаются от оценочных, это сигнал:

  • статистика не актуальна
  • условие нетипичное
  • данные распределены неравномерно
  • в запросе есть коррелированные условия, которые тяжело оценить
  • Типовые операции в планах и их смысл

    Seq Scan

    Seq Scan — последовательное чтение таблицы.

    Не всегда плохо:

  • если таблица маленькая
  • если запросу нужно много строк
  • Index Scan и Index Only Scan

  • Index Scan использует индекс, но обычно все равно обращается к таблице за остальными столбцами
  • Index Only Scan может обойтись чтением только индекса, если таблица “достаточно обслужена” и нужные данные есть в индексе
  • Виды соединений

    Для JOIN планировщик выбирает стратегию.

  • Nested Loop
  • - хорошо, когда одна сторона маленькая, а по второй есть хороший индекс
  • Hash Join
  • - хорошо для больших наборов без подходящего индекса
  • Merge Join
  • - хорошо, когда обе стороны уже отсортированы по ключу соединения или сортировка дешевая

    В PostgreSQL подробности: PostgreSQL: Planner/Optimizer

    Как тюнить запросы: практический алгоритм

    Ниже — последовательность, которая помогает не “стрелять наугад”.

  • Зафиксируйте симптом
  • - какой запрос медленный - какой SLA по времени - при каких параметрах
  • Получите план выполнения
  • - EXPLAIN (ANALYZE, BUFFERS)
  • Найдите самую дорогую часть
  • - обычно это узел с максимальным временем или большим количеством прочитанных страниц
  • Проверьте, корректны ли оценки
  • - сильное расхождение “ожидали 10 строк, получили 100000” почти всегда требует внимания
  • Улучшите доступ к данным
  • - индекс - переписывание условий - уменьшение выборки
  • Упростите результат
  • - выберите только нужные столбцы - уберите лишние JOIN - проверьте, нужен ли DISTINCT
  • Проверьте на реальных данных и повторите измерение
  • Ключевой принцип: тюнинг без измерений почти всегда превращается в ухудшение.

    Проектирование индексов от запросов

    Правильный способ думать об индексах:

  • не “на каждое поле по индексу”
  • а “под конкретные фильтры, соединения и сортировки”
  • Мини-шаблон анализа запроса

    Для каждого важного запроса выпишите:

  • фильтры WHERE
  • поля соединений JOIN ... ON
  • сортировку ORDER BY
  • группировку GROUP BY
  • И дальше принимайте решения:

  • нужен ли индекс по полям фильтра
  • нужен ли индекс по внешнему ключу
  • нужен ли составной индекс, который одновременно помогает фильтровать и сортировать
  • Партиционирование: когда таблица слишком большая

    Партиционирование — разбиение логически одной таблицы на несколько физических частей по правилу.

    Когда оно оправдано:

  • таблица очень большая
  • большинство запросов обращается к ограниченному диапазону
  • - например, заказы за последние 30 дней

    Типовой вариант — партиционирование по дате.

    Что партиционирование дает:

  • быстрее поиск по диапазону за счет “отсечения” ненужных частей
  • проще архивирование по частям
  • Что оно усложняет:

  • схему и миграции
  • некоторые виды уникальности и внешних ключей, в зависимости от СУБД и дизайна
  • В PostgreSQL: PostgreSQL: Table Partitioning

    Статистика, ANALYZE и почему планировщик может ошибаться

    Планировщик выбирает план на основе статистики:

  • сколько строк в таблице
  • сколько разных значений в колонках
  • насколько равномерно распределены данные
  • Если статистика устарела, план может стать хуже.

    В PostgreSQL статистика обновляется через ANALYZE, а автоматическое обслуживание делает autovacuum. Справка: PostgreSQL: Routine Vacuuming

    Практическая мысль:

  • “медленно после загрузки данных” часто означает “не обновили статистику”
  • Обслуживание: VACUUM, autovacuum и рост таблиц

    В PostgreSQL из-за MVCC (механизм конкурентного доступа) обновления и удаления не всегда сразу освобождают место для чтения как “пустое”. Это нормальная модель, но она требует обслуживания.

    Что важно понимать на уровне курса:

  • без обслуживания таблицы и индексы могут разрастаться
  • разрастание повышает число читаемых страниц и замедляет запросы
  • Задача проектировщика:

  • учитывать это в эксплуатации
  • не отключать обслуживание “ради скорости”, не понимая последствий
  • Антипаттерны физического проектирования

  • Индекс на каждую колонку “на всякий случай”
  • - замедляет записи и раздувает хранение
  • Игнорирование внешних ключей и их индексации
  • - соединения становятся тяжелыми, а проверки целостности дорогими
  • Тюнинг без замеров
  • - добавили индекс, стало хуже, потому что изменились планы других запросов
  • Усложнение схемы раньше времени
  • - партиционирование и сложные индексы без подтвержденной необходимости

    Мини-чеклист по итогам лекций

  • Для каждой таблицы выберите корректные типы данных
  • Для каждой связи оцените индексацию внешних ключей
  • Соберите список ключевых запросов
  • Под каждый запрос проверьте:
  • 1. фильтры 2. соединения 3. сортировки 4. нужные поля в SELECT

  • Постройте минимальный набор индексов, который покрывает критичные сценарии
  • Проверьте планы через EXPLAIN (ANALYZE, BUFFERS)
  • Убедитесь, что статистика и обслуживание включены и понятны команде эксплуатации
  • Что будет дальше

    В следующих лекциях курса обычно переходят к темам, которые связывают схему и работу системы в реальном времени:

  • транзакции и конкурентный доступ
  • уровни изоляции и блокировки
  • миграции и эволюция схемы без простоя
  • Физическое проектирование дает основу: даже идеальная логическая модель будет “тормозить”, если ей не соответствуют индексы, статистика и грамотный подход к запросам.

    5. Лекции 14–16: Транзакции, целостность, безопасность, миграции и сопровождение

    Лекции 14–16: Транзакции, целостность, безопасность, миграции и сопровождение

    Связь с предыдущими лекциями и цель финального блока

    В лекциях 1–3 мы научились работать с требованиями и жизненным циклом БД. В лекциях 4–6 построили концептуальную модель и зафиксировали бизнес-правила. В лекциях 7–10 перевели это в логическую схему с ключами, связями и нормализацией. В лекциях 11–13 сделали шаг к физическому уровню: типы данных, индексы, планы запросов и тюнинг.

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

  • транзакции и конкурентный доступ
  • целостность данных при параллельной работе
  • безопасность и разграничение прав
  • миграции и эволюция схемы без простоя
  • резервное копирование, мониторинг и сопровождение
  • Транзакции: как делать изменения безопасно

    Что такое транзакция

    Транзакция это группа операций с данными, которая должна выполниться как единое целое.

    На практике транзакция нужна, когда вы делаете несколько связанных изменений и не хотите оставлять систему в промежуточном состоянии.

    Пример сценария:

  • создать заказ
  • создать строки заказа
  • записать оплату
  • обновить статус заказа
  • Если любой шаг падает, результат должен быть предсказуемым: либо всё сделано, либо ничего.

    В PostgreSQL обзорно: PostgreSQL Transaction Management.

    ACID на прикладном языке

    Свойства ACID описывают, чего мы ожидаем от транзакций.

  • Атомарность: либо выполнены все изменения, либо ни одно
  • Согласованность: после фиксации данные соответствуют правилам
  • Изоляция: параллельные транзакции не ломают друг другу результат
  • Долговечность: подтверждённые изменения не пропадают при сбоях
  • > На уровне проектирования важно помнить: ACID не заменяет бизнес-правила, но помогает сделать так, чтобы правила не нарушались из-за частично выполненных операций.

    Типовой шаблон транзакции

    Если что-то пошло не так, вместо COMMIT выполняют ROLLBACK.

    Ошибка проектирования, которая ломает транзакционность

    Частая проблема: часть действий делается внутри транзакции, а часть вне её.

    Примеры типичных причин:

  • в коде приложения забыли обернуть весь сценарий в одну транзакцию
  • есть внешние вызовы, которые невозможно откатить так же просто, как запись в БД
  • Практический вывод для проектирования:

  • критичные бизнес-инварианты должны защищаться на уровне БД, а не только “правильным кодом”
  • если есть внешний эффект, продумывают шаблоны вроде outbox, идемпотентность, повторяемые операции
  • Изоляция и конкурентный доступ: как данные ломаются при параллельной работе

    Зачем нужны уровни изоляции

    Когда два пользователя работают одновременно, возможны аномалии чтения и обновления. Уровень изоляции определяет, какие аномалии допустимы.

    В PostgreSQL уровни изоляции описаны здесь: PostgreSQL Transaction Isolation.

    Аномалии, которые важно понимать

  • Неповторяемое чтение: вы дважды читаете одну строку, а между чтениями другая транзакция её изменила
  • Фантомное чтение: вы дважды выполняете запрос по условию, и между запросами другая транзакция добавила строки, подходящие под условие
  • Потерянное обновление: две транзакции читают одно значение и обе записывают “свою версию”, затирая результат друг друга
  • Уровень изоляции сам по себе не всегда гарантирует отсутствие потерянных обновлений, если вы проектируете сценарий как “прочитал, посчитал, записал”. Для таких ситуаций обычно применяют блокировки строк или приёмы оптимистичной конкуренции.

    Практика: блокировки строк и SELECT ... FOR UPDATE

    Когда вы хотите гарантировать, что строку нельзя параллельно изменить “в обход” вашего сценария, вы берёте блокировку.

    Ключевая мысль:

  • вы проектируете не только таблицы
  • вы проектируете типовые сценарии изменения данных
  • !Иллюстрация конкуренции двух транзакций и влияния блокировок

    Дедлоки и как снижать риск

    Дедлок это ситуация, когда транзакции взаимно ждут друг друга.

    Практические способы уменьшить риск:

  • обновлять таблицы в одинаковом порядке во всех сценариях
  • держать транзакции короткими
  • не делать долгих внешних вызовов внутри транзакции
  • блокировать строки в предсказуемом порядке
  • Целостность данных: что должно гарантировать приложение, а что БД

    Целостность как продолжение бизнес-правил

    В лекциях 4–6 мы фиксировали бизнес-правила словами, а в лекциях 7–10 превращали их в ограничения.

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

    Базовый набор ограничений, который почти всегда нужен

  • NOT NULL для обязательных атрибутов
  • UNIQUE для бизнес-уникальности
  • FOREIGN KEY для ссылочной целостности
  • CHECK для простых доменных правил
  • Справка по ограничениям: PostgreSQL Constraints.

    Отложенная проверка ограничений

    Иногда удобно проверять целостность не после каждого оператора, а в конце транзакции.

    Это полезно, когда:

  • вы временно “переставляете” связи внутри транзакции
  • порядок вставок неудобен, но итоговое состояние корректно
  • В PostgreSQL это делается через deferrable-ограничения: PostgreSQL Deferred Constraint Checking.

    Инварианты, которые не помещаются в обычные ограничения

    Есть правила, которые сложно выразить простым CHECK или FK.

    Примеры:

  • сумма оплат по заказу не должна превышать сумму заказа с учётом правил переплаты
  • статус заказа должен меняться только по допустимым переходам
  • Варианты реализации:

  • Валидация в приложении
  • Триггеры в БД
  • Отдельные таблицы состояния и ограничение операций через хранимые процедуры
  • Практическое правило:

  • если нарушение правила приводит к финансовым или юридическим рискам, лучше иметь защиту на уровне БД
  • Безопасность: доступы, изоляция данных, аудит

    Модель угроз в прикладной БД

    Даже в “внутренней” системе типовые риски похожи:

  • утечка персональных данных из-за слишком широких прав
  • случайное удаление или массовое обновление
  • доступ разработчика или аналитика к продакшен-данным без необходимости
  • SQL-инъекции на уровне приложения
  • Проектирование БД должно предусматривать защитные барьеры.

    Роли и принцип минимальных привилегий

    Хорошая практика: выдавать только те права, которые реально нужны роли.

    Типичный набор ролей:

  • роль приложения для работы в продакшене
  • роль миграций, которая может менять схему
  • роль только для чтения для аналитики
  • администраторская роль для обслуживания
  • В PostgreSQL управление ролями и правами: PostgreSQL Database Roles и PostgreSQL Privileges.

    Разделение доступа через представления

    Если одной группе пользователей нельзя видеть “сырые” таблицы, им дают доступ к VIEW.

    Плюсы:

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

    Когда разные пользователи работают с одной таблицей, но должны видеть только свои строки, применяют построчные политики.

    В PostgreSQL: PostgreSQL Row Security Policies.

    Шифрование и безопасный транспорт

    Важно различать уровни:

  • шифрование соединения клиент-БД
  • шифрование диска или хранилища
  • маскирование и минимизация данных
  • Для PostgreSQL транспортный уровень через TLS описан здесь: PostgreSQL SSL Support.

    Аудит изменений

    Аудит это ответы на вопросы:

  • кто изменил данные
  • что именно изменил
  • когда это произошло
  • Решения зависят от требований:

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

    Миграции: как эволюционировать схему без хаоса

    Что такое миграция

    Миграция это управляемое изменение схемы и связанных данных, которое можно повторяемо применять в разных окружениях.

    Ключевые свойства хороших миграций:

  • версионирование
  • воспроизводимость
  • предсказуемость отката или безопасного “движения вперёд”
  • Транзакционные миграции

    Во многих СУБД часть DDL может выполняться в транзакции. В PostgreSQL значительная часть DDL транзакционна, что снижает риск “полусозданной” схемы.

    Общий обзор DDL: PostgreSQL Data Definition.

    Практическое правило:

  • даже если DDL транзакционен, миграцию лучше делать так, чтобы её можно было безопасно перезапустить или продолжить
  • Шаблон миграций без простоя: expand and contract

    Если система работает 24/7, опасно делать миграцию, которая требует одновременного обновления и БД, и приложения.

    Безопасный общий подход:

  • Expand: добавить новые элементы схемы так, чтобы старый код продолжал работать
  • Обновить приложение так, чтобы оно начало писать и читать по-новому
  • Перенести и проверить данные
  • Contract: удалить старые элементы схемы
  • !Схема безопасной миграции без простоя

    Примеры приёмов для “живых” миграций

  • добавлять новую колонку как NULL и без строгих ограничений, затем включать ограничения позже
  • делать перенос данных отдельной задачей с контролем прогресса
  • переключать чтение на новую схему через фичефлаги
  • избегать долгих блокировок таблиц в рабочее время
  • Важно: конкретные детали зависят от СУБД, объёма данных и SLA.

    Типовые ошибки в миграциях

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

    Почему бэкап это не файл, а процесс

    Наличие бэкапа бессмысленно, если вы не проверяли восстановление.

    Минимальный набор того, что нужно согласовать:

  • как часто делаем копии
  • как быстро должны восстановиться
  • какую потерю данных считаем допустимой
  • Эти пункты обычно связывают с показателями RTO и RPO.

  • RTO это допустимое время простоя
  • RPO это допустимая потеря данных по времени
  • Логические и физические бэкапы

  • логический бэкап это выгрузка данных и схемы в формате, который можно восстановить как “пересоздание”
  • физический бэкап это копия файлового состояния БД, обычно совместимая с восстановлением до точки во времени
  • В PostgreSQL:

  • pg_dump для логического бэкапа: PostgreSQL pg_dump
  • pg_basebackup для физического бэкапа: PostgreSQL pg_basebackup
  • Практическое правило:

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

    Мониторинг и диагностика

    В эксплуатационном контуре обычно нужны:

  • мониторинг доступности
  • мониторинг задержек ключевых запросов
  • список самых медленных запросов
  • контроль роста таблиц и индексов
  • Связь с лекциями 11–13 прямая: без измерений и чтения планов запросов тюнинг превращается в угадывание.

    Регулярные практики, которые окупаются

  • регламент на создание индексов и проверку реального эффекта
  • анализ блокировок и ожиданий
  • периодическая проверка целостности справочников и ссылок
  • тестирование восстановления из бэкапов
  • контроль прав доступа и ревизия ролей
  • Итоговый чеклист по финальным лекциям

    Ниже список того, что желательно иметь к моменту запуска системы.

  • описанные бизнес-инварианты и решение, где они enforced
  • сценарии транзакций для критичных операций
  • выбранные уровни изоляции и правила блокировок для спорных сценариев
  • минимально необходимые ограничения целостности
  • модель ролей и принцип минимальных привилегий
  • аудит для критичных таблиц и операций
  • стратегия бэкапов и проверенное восстановление
  • процесс миграций с версионированием и тестовыми прогонами
  • мониторинг и процедура реакции на инциденты