Как реализована очередность файлов миграции в Django?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как реализована очередность файлов миграции в 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 в БД для отслеживания применённых миграций
- Алгоритм обхода графа для определения порядка выполнения
- Транзакции для гарантии консистентности
- Валидация циклических зависимостей
Это обеспечивает надёжное и воспроизводимое управление схемой БД.