Миграции баз данных
Разработчику
Аналитику
Тестировщику
Архитектору
Инженеру
Миграции
Понятие миграции
Ранее мы упомянули миграции базы данных. Давайте разберёмся, что это.
★ Миграции — это механизм управления изменениями структуры базы данных (схемы) в процессе разработки приложения. Они позволяют последовательно обновлять базу данных, добавляя новые таблиц, изменяя существующие или удаляя ненужные элементы, без потери данных.
Миграции представляют собой набор скриптов, которые описывают, как изменить базу данных с одного состояния на другое. Эти скрипты можно применять (прогонять) или откатывать (отменять), чтобы вернуться к предыдущему состоянию.
Миграции работают по принципу версионирования базы данных. Каждая миграция представляет собой отдельный шаг в эволюции структуры базы данных.
Этапы миграции
★ Этапы миграции.
- Создание миграции. Разработчик создаёт файл миграции, который описывает изменения в базе данных. Например, добавление новой таблицы Users или нового столбца email в таблицу Customers.
- Применение миграции. Миграция выполняется в базе данных, и её изменения применяются. Например, ORM или инструмент миграций генерирует SQL-запрос для создания таблицы или добавления столбца.
- Откат миграции. Если миграция вызвала проблемы или нужно вернуться к предыдущему состоянию, миграцию можно откатить. Например, удаление таблицы или столбца, которые были добавлены.
- Версионирование. Каждая миграция имеет уникальный идентификатор (например, временная метка или порядковый номер). База данных хранит информацию о том, какие миграции уже применены.

Задачи миграций
Миграции решают несколько практических задач:
- Управление изменениями в структуре базы данных. В процессе разработки приложения часто возникает необходимость изменить структуру БД - добавить новые таблицы или столбцы, изменить типы данных или удалить устаревшие элементы. Миграции позволяют делать это систематически и безопасно.
- Синхронизация между разработчиками и окружениями. В командной разработке несколько разработчиков могут работать над одним проектом. Миграции обеспечивают, чтобы все участники имели одинаковую структуру базы данных. Также миграции помогают синхронизировать БД в разных окружениях (разработка. тестирование. продакшен).
- Автоматизация процесса обновления базы данных. Без миграций разработчики вынуждены вручную писать и выполнять SQL-скрипты для обновления базы данных. Это может привести к ошибкам и несогласованности. Миграции автоматизируют этот процесс, делая его более надёжным.
- Откат изменений. Если новое изменение вызвало проблемы (например, сломало приложение), миграции позволяют легко откатить изменения до предыдущего состояния.
- Поддержка целостности данных. Миграции могут включать не только изменения в структуре БД, но и манипуляции с данными (например, перенос данных из одной таблицы в другую). Это помогает сохранить данные при изменении схемы.
Пример и инструменты
★ Пример работы миграций:
- Разработчик создаёт миграцию - "добавить таблицу Задачи с полями id, title, description, due_date".
- Миграция применяется - ORM или инструмент миграций генерирует SQL-запрос, а таблица сохраняется в базе данных.
- Если миграция вызвала проблемы, её можно откатить, и при этом ORM генерирует SQL-запрос для отката.
Инструменты для работы с миграциями:
- Java (Hibernate): Liquibase, Flyway.
- C# (.NET): Entity Framework Migrations (для PostgreSQL-специфики вроде
DEFERRABLE— сырой SQL вmigrationBuilder.Sql, см. PostgreSQL: отложенные ограничения и .NET). - Python: Django Migrations, Alembic (для SQLAlchemy).
- PHP: Laravel Migrations.
- Ruby on Rails: ActiveRecord Migrations.
- Go: Goose, Migrate.
- Node.js: Knex.js, Sequelize Migrations.
Анатомия файла миграции
Типичная миграция состоит из двух частей:
| Часть | Назначение |
|---|---|
| Up | применить изменение (создать таблицу, добавить столбец) |
| Down | откатить изменение (удалить столбец, вернуть старый тип) |
Пример (псевдокод, стиль EF / Alembic):
МИГРАЦИЯ 20250615_AddEmailToUsers
Up():
ДОБАВИТЬ СТОЛБЕЦ Users.Email VARCHAR(256) NULL
СОЗДАТЬ УНИКАЛЬНЫЙ ИНДЕКС IX_Users_Email ON Users(Email)
Down():
УДАЛИТЬ ИНДЕКС IX_Users_Email
УДАЛИТЬ СТОЛБЕЦ Users.Email
В SQL напрямую:
-- Up
ALTER TABLE Users ADD COLUMN Email VARCHAR(256);
CREATE UNIQUE INDEX IX_Users_Email ON Users(Email);
-- Down
DROP INDEX IX_Users_Email;
ALTER TABLE Users DROP COLUMN Email;
Down не всегда обязателен в проде (откат опасен при потере данных), но на dev он спасает при ошибочной миграции.
Таблица истории миграций
СУБД хранит, какие миграции уже выполнены:
| Система | Таблица | Пример поля |
|---|---|---|
| EF Core | __EFMigrationsHistory | MigrationId, ProductVersion |
| Django | django_migrations | app, name, applied |
| Flyway | flyway_schema_history | version, success |
| Alembic | alembic_version | одна строка с revision |
При старте database update ORM сравнивает папку Migrations/ с таблицей истории и выполняет только недостающие Up.
Безопасное добавление NOT NULL столбца
Плохо (блокировка + падение на существующих строках):
ALTER TABLE Orders ADD COLUMN Priority INT NOT NULL;
Лучше — в несколько миграций:
ADD COLUMN Priority INT NULL— быстро, без default для всех строк сразу.- Backfill:
UPDATE Orders SET Priority = 0 WHERE Priority IS NULLбатчами (см. пакетная работа). ALTER COLUMN Priority SET NOT NULLпосле проверки.- При необходимости — default только для новых строк.
На PostgreSQL 11+ ADD COLUMN ... DEFAULT для новых столбцов часто не переписывает всю таблицу — но поведение зависит от версии и типа; тяжёлые таблицы всё равно планируют в окно обслуживания.
Переименование и смена типа
Переименование столбца в zero-downtime системах:
- Добавить новый столбец
EmailAddress. - Триггер или job копирует
Email→EmailAddress. - Деплой приложения, читающего/пишущего оба поля.
- Backfill завершён → переключить только на новое поле.
- Удалить старое в отдельной миграции.
Смена типа VARCHAR → INT — только через промежуточный столбец или выражение с проверкой USING в PostgreSQL.
Миграции данных и миграции схемы
| Тип | Пример | Риск |
|---|---|---|
| Схема | новая таблица, индекс | блокировки DDL |
| Данные | разнести FullName на FirstName/LastName | долго, нужны батчи |
| Seed | роли Admin, User | тестовые данные на prod |
Seed для prod выносят в отдельные осознанные скрипты. Тестовые пользователи в миграции — частая ошибка (FAQ).
Окружения разработки (dev, test, stage, prod)
Порядок применения одинаковый, содержимое БД — нет:
dev → database update после каждого merge
test → CI накатывает миграции на ephemeral DB
stage → как prod, с копией объёма или срезом
prod → миграция отдельным шагом деплоя, бэкап до DDL
Никогда не править схему вручную на одном стенде без миграции в Git — через неделю "у Пети работает, у Маши нет".
CI/CD и миграции
Минимальный pipeline:
- Сборка приложения.
- Поднять тестовую БД (контейнер PostgreSQL).
dotnet ef database update/alembic upgrade head/flyway migrate.- Интеграционные тесты.
- На prod — тот же артефакт миграций, не "ручной SQL по SSH".
Автоприменение миграций при старте веб-приложения на prod допустимо только с жёстким контролем и бэкапом; многие команды запрещают.
Откат миграции
| Ситуация | Действие |
|---|---|
| Ошибка на dev | database update PreviousMigration или Down |
| Ошибка на prod после DDL | восстановление из бэкапа или forward-fix миграция |
| Удалили столбец с данными | Down не вернёт данные без бэкапа |
Forward-fix — новая миграция исправляет проблему вместо отката (добавили лишний индекс → миграция DropRedundantIndex).
Блокировки и большие таблицы
Операции, которые блокируют таблицу на запись:
ADD COLUMNс переписыванием всей таблицы (зависит от СУБД);CREATE INDEXбезCONCURRENTLYв PostgreSQL;- массовый
UPDATEбез батчей.
Для PostgreSQL индекс на живой системе:
CREATE INDEX CONCURRENTLY IX_Orders_CreatedAt ON Orders(CreatedAt);
В миграции EF это часто — сырой SQL в migrationBuilder.Sql(...), как для DEFERRABLE FK.
Согласование команд
При параллельной работе двух разработчиков:
- Оба создали миграцию
20250615_*— конфликт имён. - Решение: rebase, переименовать одну миграцию, прогнать на чистой БД.
- Политика: одна очередь миграций в
main, feature-ветки мержатся часто.
Code review миграции так же строго, как бизнес-код: смотрят на блокировки, потерю данных, отсутствие Down на dev.
Пример жизненного цикла фичи
Фича "теги у задач":
Миграция 1: CREATE TABLE Tags, CREATE TABLE TaskTags (many-to-many)
Миграция 2: seed справочника системных тегов (только dev/stage скрипт)
Код: сущности Task, Tag, конфигурация связи
Деплой: backup prod → migrate → smoke test → включение фичи
Если забыли миграцию — приложение падает с "relation Tags does not exist" при первом запросе.
Обзор инструментов
| Инструмент | Стиль | Особенность |
|---|---|---|
| EF Migrations | код C# | тесно с моделью EF |
| Alembic | Python | автогенерация из SQLAlchemy metadata |
| Flyway | SQL-файлы V1__, V2__ | языконезависим |
| Liquibase | XML/YAML/SQL | changelog, теги, rollbacks |
| Goose | Go, SQL | простой CLI |
| Rails | Ruby DSL | change с auto reversible |
Выбор часто диктует стек; в .NET-команде с EF разумно EF Migrations + сырой SQL для особых случаев PostgreSQL.
Пример — добавление поля Email
Контекст: таблица Users с двумя миллионами строк, нужен уникальный Email.
Миграция A — столбец пока необязательный:
// Псевдокод EF migration Up()
migrationBuilder.AddColumn<string>(
name: "Email",
table: "Users",
maxLength: 256,
nullable: true);
Job backfill — отдельный скрипт, который заполняет пустые Email пакетами по 10 000 строк (пакетная работа с данными).
Миграция B — после backfill столбец делают обязательным и добавляют уникальный индекс.
Миграция C — код приложения требует email при регистрации.
Три релиза вместо одного долгого ALTER на таблице с миллионами строк.
Конфликт двух веток Git
main: ... → M3 → M4
feature: ... → M3 → M4_feature (параллельно)
После merge две миграции с разными timestamp. Решение:
- Переименовать одну в
M5_merged_...; - Прогнать на чистой локальной БД с нуля;
- На shared dev — если обе уже применены, создать consolidating миграцию только на проблемных стендах (редко, лучше не допускать).
Профилактика: короткоживущие ветки, миграции только из main.
Порядок деплоя миграции и кода
| Изменение | Сначала | Потом |
|---|---|---|
| Новый nullable столбец | миграция | код пишет в столбец |
| Удаление столбца | код не использует | миграция DROP |
| Переименование | expand-contract (два релиза) | см. выше |
| Новая обязательная таблица | миграция | код |
Expand-contract: добавили поле → деплой кода, пишущего в оба → убрали старое поле.
Проверки в CI
- dotnet build
- docker run postgres:16
- dotnet ef database update
- dotnet test (integration)
- опционально: sqllint на сгенерированный SQL
Падение на шаге database update — миграция сломана до merge.
Документирование миграции в PR
Шаблон описания:
## Миграция AddUserEmail
- Таблицы: Users
- Блокировка: низкая (nullable column)
- Backfill: да, отдельный job #1234
- Откат: Down удаляет столбец (только dev)
- Время на prod (оценка): < 1 сек
DBA и SRE читают PR до ночного релиза.
Анти-паттерны миграций
| Проблема | Последствие |
|---|---|
| Правка уже применённой миграции | расхождение history |
DROP TABLE без бэкапа | потеря данных |
| Seed тестовых admin/password в Up | дыра на prod |
| Один огромный скрипт из 50 изменений | невозможный откат |
| Ручной hotfix на prod без файла в Git | следующий deploy ломается |
Связь с ORM-моделью
Изменили класс User → Add-Migration → просмотрите сгенерированный SQL глазами:
- лишние
DROP? - смена типа с потерей данных?
- индексы с неудачным именем?
Автогенерация — черновик, не закон.
Что запомнить
- Миграции версионируют схему БД; каждый шаг попадает в историю, а не остаётся разовым скриптом на одном сервере.
- Без воспроизводимых миграций команда быстро теряет синхрон между окружениями.
- Безопасные миграции планируют с учетом блокировок, откатов и проверки данных.
Типовые вопросы на собеседовании
- Что должно быть в хорошей миграции (Up/Down, проверки, порядок применения)?
- Почему опасно изменять большие таблицы "в один шаг"?
- Как вы интегрируете миграции в CI/CD процесс?
- Какие проверки нужны до и после применения миграции?
Мини-практикум
- Спроектируйте миграцию добавления обязательного поля в существующую таблицу без простоя.
- Опишите план отката, если новая схема ломает бизнес-операцию.
- Добавьте в командный чек-лист три обязательных проверки перед релизом миграций.