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