← Назад к вопросам

Как реализована очередность файлов миграции в Django?

2.0 Middle🔥 71 комментариев
#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как реализована очередность файлов миграции в Django

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

Основная структура файлов миграций

Миграции хранятся в директории migrations внутри приложения:

my_app/
  migrations/
    __init__.py
    0001_initial.py
    0002_add_field.py
    0003_rename_field.py
    0004_delete_model.py

Каждый файл миграции содержит класс, наследующий django.db.migrations.Migration:

from django.db import migrations, models

class Migration(migrations.Migration):
    # Зависимости от предыдущих миграций
    dependencies = [
        ('my_app', '0001_initial'),
    ]
    
    # Операции, которые нужно применить
    operations = [
        migrations.CreateModel(
            name='User',
            fields=[
                ('id', models.AutoField(primary_key=True)),
                ('name', models.CharField(max_length=100)),
            ],
        ),
    ]

Система зависимостей (Dependency Graph)

Ключевая часть очередности — это dependencies (зависимости). Каждая миграция указывает, на какой миграции она зависит:

class Migration(migrations.Migration):
    dependencies = [
        ('myapp', '0002_previous_migration'),  # Эта миграция выполняется ПОСЛЕ 0002
    ]
    
    operations = []

Это создаёт граф зависимостей (DAG — Directed Acyclic Graph):

0001_initial.py (нет зависимостей)
    ↓
0002_add_field.py (зависит от 0001)
    ↓
0003_rename_field.py (зависит от 0002)

Как Django определяет порядок

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

# Внутри django/db/migrations/executor.py
class MigrationExecutor:
    def __init__(self, connection):
        self.loader = MigrationLoader(connection)
        
    def plan(self, targets, clean_start=False):
        # Определить, какие миграции уже применены
        applied = set(self.loader.applied_migrations)
        
        # Найти цепочку миграций для применения
        migration_plan = []
        graph = self.loader.graph  # Граф зависимостей
        
        # Обход графа в правильном порядке
        for migration in graph.forwards_plan(targets[0]):
            if migration not in applied:
                migration_plan.append((migration, False))
        
        return migration_plan, forward

Таблица миграций в БД

Джанго отслеживает применённые миграции в специальной таблице:

django_migrations
+----+---------+---------------------+----------+
| id | app     | name                | applied  |
+----+---------+---------------------+----------+
| 1  | myapp   | 0001_initial        | 2024-..  |
| 2  | myapp   | 0002_add_field      | 2024-..  |
| 3  | another | 0001_initial        | 2024-..  |
+----+---------+---------------------+----------+

Джанго проверяет эту таблицу перед выполнением миграций:

class MigrationLoader:
    def load_disk(self):
        self.disk_migrations = {}
        # Загрузить все миграции с диска
        
    def record_applied(self, app_label, migration_name):
        # Записать в БД, что миграция применена
        self.connection.execute(
            "INSERT INTO django_migrations (app, name, applied) VALUES (%s, %s, %s)",
            [app_label, migration_name, now()]
        )

Пример цепочки миграций

У нас есть три приложения: users, posts, comments. Каждое имеет свои миграции:

users/migrations:
  0001_initial (User model)
  0002_add_email (добавить email)
  0003_add_phone (добавить phone)

posts/migrations:
  0001_initial (Post model + ForeignKey на User)
  0002_add_tags (добавить tags)

comments/migrations:
  0001_initial (Comment model + ForeignKey на Post)

Джанго выполняет миграции в таком порядке:

1. users/0001_initial
2. users/0002_add_email
3. users/0003_add_phone
4. posts/0001_initial  # Может быть применена только ПОСЛЕ users/0001
5. posts/0002_add_tags
6. comments/0001_initial  # Может быть применена только ПОСЛЕ posts/0001

Кросс-приложение зависимости

Иногда миграция в одном приложении зависит от миграции в другом:

# comments/migrations/0001_initial.py
from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):
    dependencies = [
        ('posts', '0001_initial'),  # Зависит от posts!
    ]
    
    operations = [
        migrations.CreateModel(
            name='Comment',
            fields=[
                ('id', models.AutoField(primary_key=True)),
                ('text', models.TextField()),
                ('post', models.ForeignKey(
                    to='posts.Post',
                    on_delete=models.CASCADE
                )),
            ],
        ),
    ]

Обнаружение циклических зависимостей

Джанго предотвращает циклические зависимости на этапе валидации:

class MigrationGraph:
    def validate_consistency(self):
        # Проверить, нет ли циклов
        for node in self.nodes:
            path = self.forwards_plan(node)
            if self._has_cycle(path):
                raise CircularDependencyError(
                    f"Циклическая зависимость обнаружена в {node}"
                )

Если есть цикл, Django выдаст ошибку:

CircularDependencyError: app 'myapp' has circular dependency:
  0001_initial -> 0002_add_relation -> 0001_initial

Команды для работы с миграциями

# Показать статус миграций
python manage.py showmigrations

# Применить все необходимые миграции
python manage.py migrate

# Откатить до определённой миграции
python manage.py migrate myapp 0002_add_field

# Создать новую миграцию
python manage.py makemigrations

# Автогенерировать миграцию
python manage.py makemigrations --auto

Пример работы с зависимостями программно

from django.db.migrations.executor import MigrationExecutor
from django.db import connections

def get_migration_plan():
    executor = MigrationExecutor(connections['default'])
    
    # Получить граф зависимостей
    graph = executor.loader.graph
    
    # Получить все миграции в правильном порядке
    plan = graph.forwards_plan(('myapp', '0005_final'))
    
    for migration in plan:
        print(f"{migration.app_label}/{migration.name}")
        print(f"  Зависит от: {migration.dependencies}")

Миграции и состояние транзакций

Каждая миграция выполняется в своей транзакции (для поддерживаемых БД):

class MigrationExecutor:
    def apply_migration(self, migration):
        with self.connection.atomic():
            # Вся миграция — в одной транзакции
            # Если ошибка — всё откатывается
            migration.apply(self.project_state, schema_editor)

Это гарантирует, что если миграция упадёт, БД останется в консистентном состоянии.

Рекомендации по управлению миграциями

Хорошие практики:

# ✅ Правильно: миграция с явными зависимостями
class Migration(migrations.Migration):
    dependencies = [
        ('myapp', '0001_initial'),
    ]
    operations = [...]

# ❌ Неправильно: пустые зависимости в новой миграции
class Migration(migrations.Migration):
    dependencies = []  # Создаёт множество корневых миграций!
    operations = [...]

# ✅ Правильно: несколько операций в одной миграции
class Migration(migrations.Migration):
    operations = [
        migrations.AddField(...),
        migrations.AddIndex(...),
        migrations.RunPython(my_data_migration),
    ]

# ❌ Неправильно: огромная миграция с сотней операций
class Migration(migrations.Migration):
    operations = [...]  # 100 операций!

Заключение

Очередность миграций в Django реализована через:

  • Граф зависимостей (DAG) между миграциями
  • Таблица django_migrations в БД для отслеживания применённых миграций
  • Алгоритм обхода графа для определения порядка выполнения
  • Транзакции для гарантии консистентности
  • Валидация циклических зависимостей

Это обеспечивает надёжное и воспроизводимое управление схемой БД.

Как реализована очередность файлов миграции в Django? | PrepBro