Каждое изменение модели в Django требует синхронизации с базой данных. Без системы миграций разработчики выполняли бы SQL-запросы вручную, что приводило к расхождениям между окружениями и ошибкам на продакшене. Миграции решают эту проблему: они версионируют схему данных, позволяют откатывать изменения и автоматизируют развертывание.
Django генерирует миграции автоматически из Python-кода моделей. Команда makemigrations создает файлы с инструкциями, а migrate применяет их к базе. Этот механизм работает с PostgreSQL, MySQL, SQLite и другими СУБД, которые поддерживает фреймворк. В этом руководстве разберем полный цикл: от первой миграции до автоматизации в CI/CD-пайплайне.
Материал ориентирован на backend-разработчиков и DevOps-инженеров, которые хотят управлять схемой данных предсказуемо. Все примеры проверены на Django 5.1 и актуальны на июль 2026 года. Если вы планируете миграцию между разными СУБД, например с MySQL на PostgreSQL, изучите пошаговый план миграции баз данных с pgloader и Flyway.
Введение: зачем нужны миграции и как они работают
Представьте команду из трех разработчиков. Один добавил поле phone в модель User, второй удалил таблицу Orders, третий изменил тип поля price. Без миграций каждый применяет свои SQL-скрипты вручную. Результат: база на продакшене не соответствует коду в репозитории, приложение падает с ошибками.
Миграции Django решают эту проблему через систему версионирования схемы. Каждая миграция - это Python-файл с уникальным именем и списком зависимостей. Фреймворк отслеживает, какие миграции уже применены, через таблицу django_migrations в базе данных. При запуске migrate Django сверяет состояние этой таблицы с файлами миграций и выполняет только недостающие.
Процесс выглядит так: вы описываете модели в models.py, запускаете makemigrations для генерации файла миграции, проверяете сгенерированный код и коммитите его в репозиторий. На сервере или в окружении коллеги выполняете migrate - и схема базы данных синхронизируется. Этот же подход работает при откате изменений: Django выполняет обратные операции, если они определены.
Для углубленного изучения внутреннего устройства миграций обратитесь к официальной документации Django. Там детально описаны классы операций и формат файлов миграций.
Создание и применение миграций: от модели к базе данных
Рабочий цикл начинается с команды python manage.py makemigrations. Django сканирует все зарегистрированные приложения, сравнивает текущее состояние моделей с последней примененной миграцией и генерирует новый файл в директории migrations/. Имя файла содержит порядковый номер и краткое описание изменений, например 0002_alter_user_email.py.
Перед коммитом всегда проверяйте сгенерированный код. Флаг --dry-run показывает, какие миграции будут созданы, без записи файлов. Флаг --verbosity 3 выводит детальный SQL, который Django планирует выполнить. Это помогает найти неожиданные изменения, например удаление индекса или смену типа колонки, которые могут заблокировать таблицу на продакшене.
Команда python manage.py migrate применяет миграции. Без аргументов она выполняет все непримененные миграции для всех приложений. Можно указать конкретное приложение: migrate auth применит миграции только для встроенной системы аутентификации. Команда showmigrations выводит список всех миграций с отметками [X] для примененных и [ ] для ожидающих.
Первая миграция: пошаговый пример
Создадим модель для хранения заметок. В файле notes/models.py:
from django.db import models
class Note(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']Запускаем генерацию миграции:
python manage.py makemigrations notesDjango создаст файл notes/migrations/0001_initial.py. Внутри - класс Migration с операцией CreateModel, где описаны все поля и их типы. Откроем этот файл и убедимся, что поле created_at имеет параметр auto_now_add=True, а не default=timezone.now - это разные вещи на уровне базы данных.
Применяем миграцию:
python manage.py migrate notesПроверяем результат в PostgreSQL:
\d notes_noteТаблица создана с колонками id, title, content, created_at и индексом по ordering. Django автоматически добавил первичный ключ id типа serial.
Миграции для изменений моделей: добавление полей, удаление, переименование
Добавим поле priority в модель Note:
priority = models.IntegerField(default=0)Запускаем makemigrations. Django сгенерирует миграцию с операцией AddField. Ключевой момент: параметр default=0 в модели транслируется в SQL-значение по умолчанию для существующих записей. Без этого параметра Django запросит значение при создании миграции, и процесс остановится в интерактивном режиме - неприемлемо для CI/CD.
Удаление поля требует осторожности. Операция RemoveField необратима: откат не восстановит данные. Если поле содержит важную информацию, сначала создайте миграцию данных, которая скопирует значения в другую таблицу или поле, и только потом удаляйте оригинал.
Переименование поля выполняется операцией RenameField. Django автоматически определяет, что поле переименовано, если старое имя исчезло, а новое появилось с тем же типом и ограничениями. Если фреймворк не распознал переименование и предлагает удалить старое поле и создать новое, укажите старое имя явно через параметр --name или отредактируйте миграцию вручную, заменив RemoveField + AddField на RenameField.
Для кастомных операций, которые Django не может сгенерировать автоматически, создавайте пустые миграции командой makemigrations --empty notes. В них можно добавить выполнение сырого SQL через migrations.RunSQL или операции с данными через migrations.RunPython.
Откат миграций: безопасный возврат к предыдущему состоянию
Откат выполняется командой migrate <app> <номер>. Номер - это префикс файла миграции, до которой нужно откатиться. Например, migrate notes 0001 откатит все миграции приложения notes после 0001. Чтобы узнать текущее состояние, используйте showmigrations: примененные миграции отмечены [X].
Откат на нулевую миграцию migrate notes zero удалит все таблицы приложения. Это полезно при пересоздании схемы на dev-окружении, но на продакшене приведет к потере данных. Всегда проверяйте, какие операции будут выполнены, с помощью флага --plan: он выводит список миграций для отката без реального выполнения.
Обратимые операции - это те, для которых Django знает обратное действие. AddField откатывается через RemoveField, CreateModel - через DeleteModel. Необратимые операции, такие как RemoveField или DeleteModel, при откате вызовут исключение IrreversibleError. В таких случаях нужно вручную восстановить данные из бэкапа или пересоздать схему.
Практический пример отката и повторного применения:
# Откатываем последнюю миграцию
python manage.py migrate notes 0001
# Проверяем состояние
python manage.py showmigrations notes
# Применяем заново
python manage.py migrate notes 0002Этот цикл полезен при тестировании миграций на staging-окружении перед деплоем на продакшен.
Откат миграций с данными: использование обратных операций
Миграции данных изменяют содержимое таблиц, а не их структуру. Они создаются через RunPython и должны иметь функцию обратного действия для безопасного отката. Пример миграции, которая заполняет поле priority на основе заголовка:
from django.db import migrations
def set_priority(apps, schema_editor):
Note = apps.get_model('notes', 'Note')
for note in Note.objects.all():
if 'urgent' in note.title.lower():
note.priority = 1
note.save(update_fields=['priority'])
def reverse_priority(apps, schema_editor):
Note = apps.get_model('notes', 'Note')
Note.objects.all().update(priority=0)
class Migration(migrations.Migration):
dependencies = [
('notes', '0002_note_priority'),
]
operations = [
migrations.RunPython(set_priority, reverse_priority),
]Функция reverse_priority сбрасывает все значения priority в 0. Без нее откат этой миграции вызовет ошибку. Тестируйте откат на копии базы данных перед деплоем: примените миграцию, проверьте данные, откатите и убедитесь, что данные вернулись в исходное состояние.
Разрешение конфликтов миграций в командной разработке
Конфликт возникает, когда две ветки создали миграции для одного приложения с одинаковым порядковым номером. Например, разработчик А создал 0003_add_tags.py, разработчик Б создал 0003_add_rating.py. При слиянии веток Django обнаруживает две миграции с номером 0003 и отказывается работать.
Обнаружить конфликт можно командой makemigrations --check. Она возвращает ненулевой код выхода, если есть несозданные миграции или конфликты. Вставьте эту проверку в CI-пайплайн, чтобы блокировать слияние веток с конфликтующими миграциями. Локально конфликт проявляется ошибкой при запуске migrate или makemigrations с сообщением о несовпадении графа зависимостей.
Автоматическое слияние миграций с makemigrations --merge
Django умеет автоматически разрешать типовые конфликты. Команда makemigrations --merge создает новую миграцию, которая объединяет две конфликтующие через зависимости. Запустите ее после слияния веток:
python manage.py makemigrations --mergeDjango создаст файл 0004_merge_0003_add_tags_0003_add_rating.py. Внутри - только зависимости от обеих конфликтующих миграций, без собственных операций. Это правильное решение для ситуаций, когда изменения не пересекаются: одно добавляет поле tags, другое - поле rating.
После создания merge-миграции проверьте граф зависимостей:
python manage.py showmigrations notesУбедитесь, что все миграции выстроены в линейную цепочку и merge-миграция ссылается на обе родительские.
Ручное разрешение сложных конфликтов
Автоматическое слияние не работает, если две миграции изменяют одно и то же поле или таблицу. Например, обе добавляют поле с одинаковым именем, но разным типом. В этом случае нужно редактировать файлы миграций вручную.
Откройте обе конфликтующие миграции и скопируйте операции из одной в другую, затем удалите лишний файл. Обновите зависимости в последующих миграциях, чтобы они ссылались на объединенный файл. Проверьте результат командой migrate --plan на тестовой базе.
Лучшие практики для предотвращения конфликтов:
- Коммитьте миграции сразу после создания, не накапливайте несколько изменений в одной ветке.
- Обсуждайте изменения моделей в pull request'ах до написания кода.
- Разделяйте приложения по функциональности: меньше пересечений - меньше конфликтов.
Оптимизация миграций для крупных проектов и нескольких баз данных
Проект с сотней миграций в одном приложении замедляет развертывание. Каждая миграция выполняется в отдельной транзакции, и накладные расходы на применение сотни мелких изменений выше, чем одного крупного. Команда squashmigrations решает эту проблему, объединяя несколько миграций в одну.
Сжатие не удаляет старые файлы - оно создает новый файл с суффиксом _squashed, который заменяет цепочку миграций. Старые файлы остаются для обратной совместимости с окружениями, где они уже применены. Django распознает сжатую миграцию и пропускает отдельные миграции, которые в нее включены.
Сжатие миграций с помощью squashmigrations
Команда для сжатия миграций приложения notes с 0001 по 0020:
python manage.py squashmigrations notes 0001 0020Django создаст файл 0001_initial_squashed_0020_auto_20260727.py. Внутри - все операции из указанного диапазона, оптимизированные и объединенные. Параметр replaces в классе Migration перечисляет исходные миграции, которые заменяются сжатой версией.
После создания сжатой миграции проверьте ее вручную. Django может некорректно объединить операции с данными или кастомным SQL. Запустите тестовое применение на копии базы и сравните схему с оригинальной цепочкой миграций. Команда sqlmigrate notes 0001_squashed покажет итоговый SQL.
Работа с несколькими базами данных: роутеры и маршрутизация миграций
Django поддерживает работу с несколькими базами данных через настройку DATABASES и классы-роутеры. Типичный сценарий: основное приложение хранит данные в PostgreSQL, а логи и аналитика - в отдельной базе на том же сервере или в ClickHouse.
Конфигурация в settings.py:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'main_db',
},
'analytics': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'analytics_db',
}
}
DATABASE_ROUTERS = ['myproject.routers.AnalyticsRouter']Класс роутера определяет, в какую базу направлять чтение, запись и миграции для каждого приложения:
class AnalyticsRouter:
route_app_labels = {'analytics', 'logs'}
def db_for_read(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'analytics'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'analytics'
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label in self.route_app_labels:
return db == 'analytics'
return db == 'default'Метод allow_migrate критически важен: он гарантирует, что миграции приложения analytics никогда не попадут в базу default, и наоборот. Применение миграций с указанием базы:
python manage.py migrate --database=analytics
python manage.py migrate --database=defaultБез указания флага --database миграции применяются только к базе default. Это частая причина ошибок, когда разработчик создает таблицы не в той базе.
Автоматизация миграций в CI/CD и best practices
Интеграция миграций в пайплайн развертывания предотвращает деплой кода с несоответствующей схемой базы данных. Базовый сценарий: разработчик создает миграции в ветке, CI проверяет их корректность, после слияния CD применяет миграции к staging и production окружениям.
Стратегия применения миграций зависит от архитектуры проекта. При blue-green деплое миграции должны быть обратно совместимы: новый код работает со старой схемой, старый код - с новой. Это значит, что нельзя удалять поля в той же миграции, где они перестали использоваться в коде. Сначала деплоите код, который игнорирует поле, затем создаете отдельную миграцию для удаления.
Флаг --plan выводит список миграций, которые будут применены, без реального выполнения. Используйте его в CD-скриптах для логирования перед запуском migrate. При сбое миграции пайплайн должен останавливаться и оповещать команду, а не продолжать деплой с несогласованной схемой.
Проверка миграций в CI-пайплайне
Пример скрипта для GitLab CI, который блокирует merge request с отсутствующими миграциями:
check_migrations:
stage: test
script:
- pip install -r requirements.txt
- python manage.py makemigrations --check --dry-run
allow_failure: falseФлаг --check возвращает ненулевой код выхода, если есть несгенерированные миграции. Флаг --dry-run гарантирует, что файлы не будут созданы в CI-окружении. Эта проверка ловит ситуацию, когда разработчик изменил модель, но забыл запустить makemigrations и закоммитить результат.
Для GitHub Actions конфигурация аналогична:
- name: Check migrations
run: |
python manage.py makemigrations --check --dry-runБезопасное применение миграций на продакшене
Перед каждым деплоем создавайте бэкап базы данных. Миграции Django выполняются в транзакциях для баз, которые это поддерживают (PostgreSQL, MySQL с InnoDB). Если миграция падает, транзакция откатывается, и схема остается в согласованном состоянии. Но это не защищает от логических ошибок в миграциях данных, которые могут испортить содержимое таблиц.
Рекомендации для продакшена:
- Применяйте миграции через
migrate --planдля предпросмотра, затемmigrateбез флагов. - Тестируйте миграции на копии продакшен-базы перед деплоем. Размер данных влияет на время выполнения: добавление индекса на таблицу с миллионами записей может занять минуты.
- Настройте мониторинг времени выполнения миграций. Аномально долгая миграция - сигнал к остановке и ручному анализу.
- Храните все файлы миграций в системе контроля версий. Не редактируйте уже примененные миграции - это приведет к расхождению хешей и ошибке InconsistentMigrationHistory.
Если проект использует облачную инфраструктуру, рассмотрите Timeweb Cloud с управляемыми базами данных, где бэкапы создаются автоматически перед обновлениями схемы. Это снижает риск потери данных при неудачной миграции.
Диагностика и исправление типичных ошибок миграций
Ошибки миграций делятся на три категории: проблемы синхронизации состояния, конфликты зависимостей и сбои в миграциях данных. Каждая имеет четкий алгоритм диагностики и исправления.
InconsistentMigrationHistory: как восстановить синхронизацию
Эта ошибка возникает, когда таблица django_migrations содержит записи о примененных миграциях, которые отсутствуют в файловой системе, или наоборот. Типичный сценарий: разработчик восстановил базу из дампа, но дамп был сделан до применения миграции, которая уже записана в django_migrations.
Решение - команда migrate --fake. Она помечает миграцию как примененную в таблице django_migrations, но не выполняет SQL-операции. Пример:
python manage.py migrate notes 0003 --fakeЭта команда полезна, когда схема базы уже соответствует миграции, но запись в django_migrations отсутствует. Обратная ситуация - миграция применена физически, но не записана - решается так же: --fake синхронизирует таблицу с реальным состоянием схемы.
Если ошибка возникла из-за удаленного файла миграции, восстановите его из системы контроля версий. Не пытайтесь править таблицу django_migrations вручную через SQL - это нарушит целостность графа зависимостей.
Ошибки в миграциях данных: отладка и безопасное выполнение
Миграции данных через RunPython выполняются внутри транзакции на PostgreSQL и MySQL. Если миграция падает с исключением, транзакция откатывается, и данные остаются нетронутыми. Проблема возникает, когда миграция выполняется успешно, но изменяет данные некорректно из-за логической ошибки.
Методы безопасной работы с миграциями данных:
- Оборачивайте код в
try-exceptи логируйте ошибки. Не позволяйте миграции молча завершаться с частично измененными данными. - Используйте
apps.get_model()вместо импорта модели напрямую. Прямой импорт может использовать актуальную версию модели, которая не соответствует состоянию базы на момент применения миграции. - Тестируйте миграции на копии продакшен-базы. Размер и структура данных на staging могут отличаться, и миграция, работающая на 1000 записей, может упасть на 10 миллионах из-за таймаута или нехватки памяти.
Если миграция данных завершилась с ошибкой и транзакция откатилась, исправьте код миграции и запустите ее снова. Если ошибка обнаружена позже, восстановите базу из бэкапа, исправьте миграцию и примените заново. Не создавайте новые миграции для исправления данных, испорченных предыдущей миграцией, - это запутывает историю и усложняет откат.
Для комплексных проектов миграции баз данных часто становятся частью более крупного процесса переноса инфраструктуры. Если вы планируете масштабный переход между СУБД, изучите фреймворк управления рисками IT-миграции с готовыми шаблонами RACI. Там разобраны этапы планирования, которые применимы и к миграциям схемы Django.