Миграции: версионирование схемы БД

Миграции — это система контроля версий для структуры базы. Вы меняете модели в Python, а Django генерирует и применяет нужные изменения схемы.
Суть: makemigrations превращает изменения моделей в файлы-инструкции, migrate применяет их к базе. Миграции версионируются и хранятся в git вместе с кодом.

Зачем нужны миграции

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

Две команды

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

python manage.py makemigrations
python manage.py migrate

Поток выглядит так:

Меняем models.py
      │
      ▼
makemigrations ── создаёт blog/migrations/0002_xxx.py
      │            (Python-описание: что добавить/изменить)
      ▼
migrate ──────── выполняет SQL: ALTER TABLE / CREATE INDEX
      │
      ▼
django_migrations ── служебная таблица: какие миграции применены

Django помнит применённые миграции в служебной таблице django_migrations, поэтому migrate можно вызывать многократно — он применит только новое.

Что внутри файла миграции

Файл миграции — обычный Python. Он содержит список операций (CreateModel, AddField, AlterField) и зависимости от предыдущих миграций. Обычно его не пишут руками — Django генерирует автоматически. Посмотреть, какой SQL будет выполнен, помогает команда sqlmigrate:

python manage.py sqlmigrate blog 0001

Как это работает под капотом

Django строит граф зависимостей миграций: каждая миграция знает, после какой она идёт. Это направленный граф — как список задач, которые нужно выполнить в правильном порядке. Такую задачу топологической сортировки легко смоделировать:

# Попробуй сам ▶ — порядок применения миграций (топосорт)
deps = {
    "0001_initial":   [],
    "0002_add_slug":  ["0001_initial"],
    "0003_add_index": ["0002_add_slug"],
    "0004_author_fk": ["0001_initial"],
}

applied, order = set(), []
def apply(name):
    if name in applied:
        return
    for parent in deps[name]:
        apply(parent)
    applied.add(name)
    order.append(name)

for m in deps:
    apply(m)

print("Порядок применения:")
for i, m in enumerate(order, 1):
    print(f"  {i}. {m}")

Django делает ровно это: сначала применяет «родителей», потом «детей», чтобы база всегда была в согласованном состоянии.

Откат и сброс

Миграции обратимы: migrate blog 0002 откатит базу до состояния второй миграции. Это полезно при экспериментах. На проде откатывать опасно, если миграция удаляла данные.

Data-миграции: изменение самих данных

Иногда нужно не поменять структуру, а преобразовать существующие данные: заполнить новое поле, разбить одно поле на два, нормализовать значения. Для этого создают пустую миграцию через makemigrations --empty и пишут операцию RunPython с двумя функциями — «вперёд» и «назад» (для отката). Важно: внутри data-миграции модель берут не импортом, а через apps.get_model("blog", "Post") — так вы получаете «историческую» версию модели, соответствующую этому моменту в истории миграций, а не текущему коду. Иначе при будущих изменениях модели старая миграция сломается. Это тонкий, но важный момент: миграции должны оставаться воспроизводимыми спустя месяцы, даже когда модель уже выглядит иначе.

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

  • Менять модель и забыть makemigrations. База останется со старой схемой, появятся ошибки «no such column».
  • Не коммитить файлы миграций в git. Тогда на сервере и у коллег схема разойдётся.
  • Редактировать уже применённую миграцию. Django запутается. Создавайте новую.
  • Удалять файлы миграций, чтобы «начать заново». На проде это рассинхронизирует django_migrations и реальную схему.
  • Добавлять обязательное поле без default к таблице с данными. Django спросит, чем заполнить старые строки.

Best practices

  • Делайте маленькие миграции и осмысленно их называйте: makemigrations --name add_post_slug.
  • Всегда коммитьте миграции вместе с изменением модели — это часть истории.
  • Перед деплоем прогоняйте migrate на копии прод-базы.
  • Сложные изменения данных делайте отдельной data-миграцией с RunPython.

Итоги

Миграции — версионирование схемы базы. makemigrations фиксирует изменения моделей в файлы, migrate применяет их. Файлы хранятся в git, применяются в порядке зависимостей, и гарантируют, что схема одинакова везде. Теперь научимся доставать из базы данные.

Проверьте себя
1. Что делает команда makemigrations?
AСразу меняет таблицы в базе
BСоздаёт файл-инструкцию с описанием изменений модели
CУдаляет базу данных
DЗапускает сервер
2. Почему файлы миграций нужно хранить в git?
AЧтобы ускорить сервер
BЧтобы схема базы была одинаковой у всех разработчиков и на сервере
CGit требует этого
DЧтобы скрыть пароли