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

Зачем нужен механизм миграций в Django?

1.6 Junior🔥 261 комментариев
#Django#Базы данных (SQL)

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

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

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

Django Миграции — управление эволюцией БД

Миграции в Django — это система управления изменениями схемы базы данных. Это критически важный инструмент для команд разработчиков и production систем.

Основная проблема без миграций

Без Django миграций (ужас)

-- Разработчик 1 меняет схему
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20);

-- Разработчик 2 независимо делает:
ALTER TABLE users ADD COLUMN country VARCHAR(50);

-- При merge коммитов — конфликты
-- При deployment в production — ???
-- Кто-то забыл migration script — база рассинхронизирована

Результат: chaos и downtime.

Что решают миграции

1. Версионирование схемы БД

Каждое изменение модели создает версионированную миграцию:

# Миграция автоматически создается
python manage.py makemigrations

# Создается файл: migrations/0001_initial.py
# Это как git commit для БД

2. Воспроизводимость

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

class Migration(migrations.Migration):
    initial = True

    dependencies = []

    operations = [
        migrations.CreateModel(
            name='User',
            fields=[
                ('id', models.AutoField(primary_key=True)),
                ('email', models.EmailField(unique=True)),
                ('created_at', models.DateTimeField(auto_now_add=True)),
            ],
        ),
    ]

Теперь любой, в любой момент, может:

python manage.py migrate  # Применить все миграции

И база будет точно такой же, как на продакшене.

3. Пошаговое развертывание (Zero Downtime)

# миграция 1: добавляем новое поле с дефолтом
class Migration(migrations.Migration):
    operations = [
        migrations.AddField(
            model_name='user',
            name='phone_number',
            field=models.CharField(
                max_length=20,
                null=True,  # Может быть пусто
                blank=True
            ),
        ),
    ]

Теперь мы можем:

# Обновить код на production
class User(models.Model):
    email = models.EmailField()
    phone_number = models.CharField(max_length=20, null=True)

# Миграция применится БЕЗ downtime'а

4. Откат (Rollback)

# Что-то сломалось? Откатываемся
python manage.py migrate users 0001

# Или посмотрим историю
python manage.py showmigrations users

# applied (X) migrations
[X] 0001_initial
[X] 0002_add_phone_field
[X] 0003_add_country_field  <- сломалась, откатываем
[ ] 0004_fix_schema

Практические примеры

Пример 1: Добавление нового поля

# models.py
class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    # Добавляем новое поле
    sku = models.CharField(max_length=50, unique=True)
# Django автоматически создаст миграцию
python manage.py makemigrations
# Created migration products/migrations/0002_product_sku.py

# Применяем
python manage.py migrate
# Running migrations:
#   Applying products.0002_product_sku ... OK

Пример 2: Пользовательская миграция (Data Migration)

python manage.py makemigrations --empty products --name populate_sku
# migrations/0003_populate_sku.py
from django.db import migrations
import uuid

def populate_sku(apps, schema_editor):
    Product = apps.get_model('products', 'Product')
    
    for product in Product.objects.all():
        if not product.sku:
            # Генерируем SKU из ID и названия
            product.sku = f"SKU-{product.id}-{uuid.uuid4().hex[:6]}"
            product.save()

def reverse_populate(apps, schema_editor):
    Product = apps.get_model('products', 'Product')
    Product.objects.all().update(sku=None)

class Migration(migrations.Migration):
    dependencies = [
        ('products', '0002_product_sku'),
    ]

    operations = [
        migrations.RunPython(populate_sku, reverse_populate),
    ]

Пример 3: Переименование поля

# Старое имя: publish_date -> Новое: published_at
python manage.py makemigrations --name rename_publish_date
# migrations/0004_rename_publish_date.py
from django.db import migrations

class Migration(migrations.Migration):
    dependencies = [
        ('blog', '0003_populate_sku'),
    ]

    operations = [
        migrations.RenameField(
            model_name='article',
            old_name='publish_date',
            new_name='published_at',
        ),
    ]

Пример 4: Удаление поля

# Сначала: удаляем из models.py поле
class User(models.Model):
    email = models.EmailField()
    # phone_number удалили

# Django создаст миграцию
python manage.py makemigrations
# Created migration users/migrations/0005_user_remove_phone_number.py

Пример 5: Создание индекса

# migrations/0006_add_indexes.py
from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = [
        ('products', '0005_product_sku'),
    ]

    operations = [
        migrations.AddIndex(
            model_name='product',
            index=models.Index(
                fields=['sku'],
                name='products_sku_idx'
            ),
        ),
        migrations.AddIndex(
            model_name='product',
            index=models.Index(
                fields=['name', 'price'],
                name='products_name_price_idx'
            ),
        ),
    ]

Workflow с миграциями

1. Разработчик меняет model
   ↓
2. python manage.py makemigrations  (создает файл миграции)
   ↓
3. python manage.py migrate  (применяет локально)
   ↓
4. Тестирует работу
   ↓
5. git commit (включает миграцию в коммит)
   ↓
6. git push (в репозиторий)
   ↓
7. Deploy в production
   ↓
8. python manage.py migrate (на сервере)

Best Practices

1. Всегда commit миграции

# ❌ ПЛОХО
echo "migrations/" >> .gitignore

# ✓ ХОРОШО
git add migrations/0001_initial.py
git commit -m "Add users model with migrations"

2. Давай описательные имена

# ❌ Плохо
0002_auto_2024_03_22_1230.py

# ✓ Хорошо
0002_add_email_verification_field.py
0003_add_user_preferences_model.py

3. Pequeные, логичные миграции

# ❌ Одна огромная миграция
class Migration(migrations.Migration):
    operations = [
        migrations.CreateModel(...),      # 50 полей
        migrations.CreateModel(...),      # 30 полей
        migrations.CreateModel(...),      # 20 полей
    ]

# ✓ Несколько логичных
# 0001_initial.py - базовые модели
# 0002_add_user_profile.py - профиль
# 0003_add_email_verification.py - верификация

4. Проверяй состояние

python manage.py showmigrations
python manage.py sqlmigrate users 0001
python manage.py migrate --plan

5. Тестируй откаты

# Применяем
python manage.py migrate

# Откатываем на шаг назад
python manage.py migrate users 0002

# Снова применяем
python manage.py migrate

Проблемы и решения

Конфликт миграций в git

# Два разработчика создали 0005_migration.py
# Решение: переименовать одну

# Переименуем вторую на 0006 и обновим dependencies
git mv migrations/0005_migration.py migrations/0006_migration.py

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

# На production есть миграция, которой нет в коде
python manage.py migrate --fake users 0005
# --fake говорит: считай что уже применена, но не выполняй SQL

Альтернатива: Alembic (для SQLAlchemy)

Для non-Django проектов используют Alembic:

alembic revision --autogenerate -m "Add user table"
alembic upgrade head  # Применить
alembic downgrade -1  # Откатить

Но в Django используем встроенные миграции.

Итог

Дjango миграции — это version control для БД:

  1. Воспроизводимость — одна команда и БД синхронизирована
  2. История — видна каждая запись в схему
  3. Командная работа — конфликты видны и разрешимы
  4. Zero downtime — правильная стратегия миграций
  5. Откат — если что-то сломалось, можно вернуться
  6. Автоматизация — Django генерирует большую часть

Это обязательный практика в production разработке.

Зачем нужен механизм миграций в Django? | PrepBro