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

Что такое миграция базы данных?

2.0 Middle🔥 221 комментариев
#Базы данных (SQL)

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

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

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

Миграция базы данных (Database Migration)

Миграция БД — это контролируемое изменение схемы базы данных (структуры таблиц, колонок, индексов и т.д.). Это инструмент для управления версионированием БД, позволяющий отслеживать и применять изменения в коде.

Проблема без миграций

-- Разработчик 1: добавляет колонку email
ALTER TABLE users ADD COLUMN email VARCHAR(255);

-- Разработчик 2: добавляет колонку phone
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- На боевом сервере может быть только первое изменение
-- На локальном компьютере — только второе
-- CHAOS! Никто не знает, какие изменения уже применены

Решение: миграции с версионированием

migrations/
├── 0001_create_users_table.sql
├── 0002_add_email_column.sql
├── 0003_add_phone_column.sql
└── 0004_create_posts_table.sql

Кажда миграция содержит:

  • UP (forward) — SQL для добавления изменений
  • DOWN (rollback) — SQL для отката изменений

Инструменты для миграций

1. Alembic (SQLAlchemy)

Самый популярный инструмент для Python + SQLAlchemy:

# Инициализация проекта
alembic init migrations

# Генерация миграции (автоматическая на основе моделей)
alembic revision --autogenerate -m "Add users table"

# Применение миграций
alembic upgrade head  # Применить все
alembic upgrade +1    # Применить одну миграцию

# Откат миграций
alembic downgrade base  # Откатить все
alembic downgrade -1    # Откатить одну

Пример сгенерированной миграции Alembic:

# migrations/versions/001_add_users_table.py
from alembic import op
import sqlalchemy as sa

def upgrade():
    op.create_table(
        "users",
        sa.Column("id", sa.Integer, primary_key=True),
        sa.Column("username", sa.String(50), unique=True),
        sa.Column("email", sa.String(255)),
        sa.Column("created_at", sa.DateTime, default=sa.func.now())
    )

def downgrade():
    op.drop_table("users")

2. Flyway (Java-based, но работает везде)

migrations/
├── V1__Create_users_table.sql
├── V2__Add_email_column.sql
└── V3__Create_posts_table.sql
-- migrations/V1__Create_users_table.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

3. Goose (Go-based, хороший для PostgreSQL)

goose -dir migrations postgres "postgres://user:pass@localhost/db" up
goose -dir migrations postgres "postgres://user:pass@localhost/db" down

Практический пример: полный цикл разработки

Шаг 1: Изменяем модель SQLAlchemy

# models.py
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True)
    username = Column(String(50), unique=True, nullable=False)
    email = Column(String(255))
    created_at = Column(DateTime, server_default="CURRENT_TIMESTAMP")

Шаг 2: Генерируем миграцию

alembic revision --autogenerate -m "Add users table"

Шаг 3: Проверяем сгенерированную миграцию

# alembic/versions/abc123_add_users_table.py
from alembic import op
import sqlalchemy as sa

def upgrade():
    op.create_table(
        "users",
        sa.Column("id", sa.Integer, nullable=False),
        sa.Column("username", sa.String(50), nullable=False),
        sa.Column("email", sa.String(255)),
        sa.Column("created_at", sa.DateTime, server_default="CURRENT_TIMESTAMP"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("username")
    )

def downgrade():
    op.drop_table("users")

Шаг 4: Применяем миграцию локально

alembic upgrade head

Шаг 5: Коммитим и деплоим на продакшн

git add alembic/versions/
git commit -m "Add users table migration"
git push

# На боевом сервере
alembic upgrade head  # Автоматически применяется при деплое

История миграций (версионирование)

Алембик ведёт таблицу с историей:

-- Таблица alembic_version
SELECT * FROM alembic_version;
-- Результат:
-- version_num
-- 001_add_users_table
-- 002_add_email_column
-- 003_create_posts_table

Это позволяет:

  • Знать, какие миграции уже применены
  • Откатить только нужные миграции
  • Применить только новые миграции

Типичные операции при миграции

from alembic import op
import sqlalchemy as sa

def upgrade():
    # Создание таблицы
    op.create_table("posts", ...)
    
    # Добавление колонки
    op.add_column("users", sa.Column("phone", sa.String(20)))
    
    # Изменение типа колонки
    op.alter_column("posts", "title", existing_type=sa.String(100), type_=sa.String(255))
    
    # Создание индекса
    op.create_index("idx_users_email", "users", ["email"])
    
    # Удаление колонки
    op.drop_column("users", "old_field")
    
    # Переименование колонки
    op.alter_column("users", "old_name", new_column_name="new_name")

def downgrade():
    # Откатываем в обратном порядке
    op.drop_table("posts")
    op.drop_column("users", "phone")
    # и т.д.

Best Practices

✅ Хорошо:

# Каждая миграция делает одно (ONE CHANGE PER MIGRATION)
def upgrade():
    # Либо добавляем колонку
    op.add_column("users", sa.Column("email", sa.String(255)))
    # Либо создаём индекс — но не оба сразу в одной миграции

❌ Плохо:

def upgrade():
    # Несколько изменений в одной миграции — сложно откатывать
    op.add_column("users", sa.Column("email", sa.String(255)))
    op.create_index("idx_email", "users", ["email"])
    op.create_table("posts", ...)

✅ Хорошо:

# Миграция должна быть идемпотентна (безопасно применять дважды)
def upgrade():
    op.create_table(
        "users",
        sa.Column("id", sa.Integer, primary_key=True),
        # Явно указываем constraints
    )

✅ Хорошо:

# Протестируй миграцию UP и DOWN
def upgrade():
    op.add_column("users", sa.Column("email", sa.String(255)))

def downgrade():
    op.drop_column("users", "email")

# Тест:
# 1. Запусти upgrade
# 2. Проверь, что схема изменилась
# 3. Запусти downgrade
# 4. Проверь, что вернулось в исходное состояние

Проблема с миграциями: блокировки

-- На таблице с 1000000 строк это может заблокировать БД на часы!
ALTER TABLE huge_table ADD COLUMN new_column VARCHAR(255) NOT NULL DEFAULT value;

Решение: расщеплённая миграция

# Миграция 1: Добавляем колонку БЕЗ DEFAULT (быстро)
def upgrade():
    op.add_column("huge_table", sa.Column("new_column", sa.String(255), nullable=True))

def downgrade():
    op.drop_column("huge_table", "new_column")

# После деплоя: Заполняем данные приложением (asyncio, batches)
# Миграция 2: Добавляем NOT NULL ограничение
def upgrade():
    op.alter_column("huge_table", "new_column", nullable=False)

Вывод

Миграции БД — это версионирование схемы. Они обеспечивают:

  • Отслеживание всех изменений
  • Воспроизводимость (любой разработчик может создать БД с нужной версией)
  • Безопасные откаты при проблемах
  • Консистентность между разработкой и продакшном

Всегда используй миграции — это стандарт в профессиональной разработке!

Что такое миграция базы данных? | PrepBro