Перейти к основному содержимому

Миграции баз данных

Разработчику Аналитику Тестировщику
Архитектору Инженеру


Миграции

Понятие миграции

Ранее мы упомянули миграции базы данных. Давайте разберёмся, что это.

Миграции — это механизм управления изменениями структуры базы данных (схемы) в процессе разработки приложения. Они позволяют последовательно обновлять базу данных, добавляя новые таблиц, изменяя существующие или удаляя ненужные элементы, без потери данных.

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

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


Этапы миграции

Этапы миграции.

  1. Создание миграции. Разработчик создаёт файл миграции, который описывает изменения в базе данных. Например, добавление новой таблицы Users или нового столбца email в таблицу Customers.
  2. Применение миграции. Миграция выполняется в базе данных, и её изменения применяются. Например, ORM или инструмент миграций генерирует SQL-запрос для создания таблицы или добавления столбца.
  3. Откат миграции. Если миграция вызвала проблемы или нужно вернуться к предыдущему состоянию, миграцию можно откатить. Например, удаление таблицы или столбца, которые были добавлены.
  4. Версионирование. Каждая миграция имеет уникальный идентификатор (например, временная метка или порядковый номер). База данных хранит информацию о том, какие миграции уже применены.

image-5.png


Задачи миграций

Миграции решают несколько практических задач:

  1. Управление изменениями в структуре базы данных. В процессе разработки приложения часто возникает необходимость изменить структуру БД - добавить новые таблицы или столбцы, изменить типы данных или удалить устаревшие элементы. Миграции позволяют делать это систематически и безопасно.
  2. Синхронизация между разработчиками и окружениями. В командной разработке несколько разработчиков могут работать над одним проектом. Миграции обеспечивают, чтобы все участники имели одинаковую структуру базы данных. Также миграции помогают синхронизировать БД в разных окружениях (разработка. тестирование. продакшен).
  3. Автоматизация процесса обновления базы данных. Без миграций разработчики вынуждены вручную писать и выполнять SQL-скрипты для обновления базы данных. Это может привести к ошибкам и несогласованности. Миграции автоматизируют этот процесс, делая его более надёжным.
  4. Откат изменений. Если новое изменение вызвало проблемы (например, сломало приложение), миграции позволяют легко откатить изменения до предыдущего состояния.
  5. Поддержка целостности данных. Миграции могут включать не только изменения в структуре БД, но и манипуляции с данными (например, перенос данных из одной таблицы в другую). Это помогает сохранить данные при изменении схемы.

Пример и инструменты

★ Пример работы миграций:

  1. Разработчик создаёт миграцию - "добавить таблицу Задачи с полями id, title, description, due_date".
  2. Миграция применяется - ORM или инструмент миграций генерирует SQL-запрос, а таблица сохраняется в базе данных.
  3. Если миграция вызвала проблемы, её можно откатить, и при этом 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__EFMigrationsHistoryMigrationId, ProductVersion
Djangodjango_migrationsapp, name, applied
Flywayflyway_schema_historyversion, success
Alembicalembic_versionодна строка с revision

При старте database update ORM сравнивает папку Migrations/ с таблицей истории и выполняет только недостающие Up.


Безопасное добавление NOT NULL столбца

Плохо (блокировка + падение на существующих строках):

ALTER TABLE Orders ADD COLUMN Priority INT NOT NULL;

Лучше — в несколько миграций:

  1. ADD COLUMN Priority INT NULL — быстро, без default для всех строк сразу.
  2. Backfill: UPDATE Orders SET Priority = 0 WHERE Priority IS NULL батчами (см. пакетная работа).
  3. ALTER COLUMN Priority SET NOT NULL после проверки.
  4. При необходимости — default только для новых строк.

На PostgreSQL 11+ ADD COLUMN ... DEFAULT для новых столбцов часто не переписывает всю таблицу — но поведение зависит от версии и типа; тяжёлые таблицы всё равно планируют в окно обслуживания.


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

Переименование столбца в zero-downtime системах:

  1. Добавить новый столбец EmailAddress.
  2. Триггер или job копирует EmailEmailAddress.
  3. Деплой приложения, читающего/пишущего оба поля.
  4. Backfill завершён → переключить только на новое поле.
  5. Удалить старое в отдельной миграции.

Смена типа VARCHARINT — только через промежуточный столбец или выражение с проверкой 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:

  1. Сборка приложения.
  2. Поднять тестовую БД (контейнер PostgreSQL).
  3. dotnet ef database update / alembic upgrade head / flyway migrate.
  4. Интеграционные тесты.
  5. На prod — тот же артефакт миграций, не "ручной SQL по SSH".

Автоприменение миграций при старте веб-приложения на prod допустимо только с жёстким контролем и бэкапом; многие команды запрещают.


Откат миграции

СитуацияДействие
Ошибка на devdatabase 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.


Согласование команд

При параллельной работе двух разработчиков:

  1. Оба создали миграцию 20250615_* — конфликт имён.
  2. Решение: rebase, переименовать одну миграцию, прогнать на чистой БД.
  3. Политика: одна очередь миграций в 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
AlembicPythonавтогенерация из SQLAlchemy metadata
FlywaySQL-файлы V1__, V2__языконезависим
LiquibaseXML/YAML/SQLchangelog, теги, rollbacks
GooseGo, SQLпростой CLI
RailsRuby DSLchange с 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. Решение:

  1. Переименовать одну в M5_merged_...;
  2. Прогнать на чистой локальной БД с нуля;
  3. На 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-моделью

Изменили класс UserAdd-Migration → просмотрите сгенерированный SQL глазами:

  • лишние DROP?
  • смена типа с потерей данных?
  • индексы с неудачным именем?

Автогенерация — черновик, не закон.


Что запомнить

  • Миграции версионируют схему БД; каждый шаг попадает в историю, а не остаётся разовым скриптом на одном сервере.
  • Без воспроизводимых миграций команда быстро теряет синхрон между окружениями.
  • Безопасные миграции планируют с учетом блокировок, откатов и проверки данных.

Типовые вопросы на собеседовании

  1. Что должно быть в хорошей миграции (Up/Down, проверки, порядок применения)?
  2. Почему опасно изменять большие таблицы "в один шаг"?
  3. Как вы интегрируете миграции в CI/CD процесс?
  4. Какие проверки нужны до и после применения миграции?

Мини-практикум

  1. Спроектируйте миграцию добавления обязательного поля в существующую таблицу без простоя.
  2. Опишите план отката, если новая схема ломает бизнес-операцию.
  3. Добавьте в командный чек-лист три обязательных проверки перед релизом миграций.