数据库迁移工具 Alembic:版本化管理你的数据库表结构

📂 所属阶段:第三阶段 — 数据持久化(数据库篇)
🔗 相关章节:SQLAlchemy 2.0 实战 · 依赖注入系统


1. Alembic 是什么?

1.1 为什么需要 Alembic?

  • 手动改表ALTER TABLE 直接在生产数据库执行,无法回滚,风险极高
  • Django migrate:耦合 Django,不适合 FastAPI 项目
  • Alembic:轻量、灵活、与 SQLAlchemy 无缝集成,支持版本化、迁移、回滚

1.2 安装与初始化

pip install alembic
cd your_project
alembic init alembic

初始化后生成:

project/
├── alembic/
│   ├── env.py          # 迁移环境配置
│   ├── script.py.mako  # 迁移文件模板
│   └── versions/       # 迁移文件存放目录
├── alembic.ini         # 配置文件
└── models.py           # 你的模型定义

2. 配置 env.py

2.1 基础配置

# alembic/env.py
from logging.config import fileConfig
from sqlalchemy import engine_from_config, pool
from alembic import context

# 导入你的模型(重要!Alembic 据此生成迁移脚本)
from models import Base  # SQLAlchemy Base
from config import get_settings

config = context.config
settings = get_settings()

# 设置数据库 URL
config.set_main_option("sqlalchemy.url", settings.database_url)

if config.config_file_name is not None:
    fileConfig(config.config_file_name)

target_metadata = Base.metadata


def run_migrations_offline() -> None:
    """离线模式:生成 SQL 脚本,不连接数据库"""
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )
    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online() -> None:
    """在线模式:直接连接数据库执行迁移"""
    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )
    with connectable.connect() as connection:
        context.configure(connection=connection, target_metadata=target_metadata)
        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

3. 生成和运行迁移

3.1 自动生成迁移

# 对比模型与当前数据库,自动生成迁移脚本
alembic revision --autogenerate -m "add user avatar column"

# 参数说明:
# --autogenerate:自动对比模型差异
# -m:添加迁移说明

生成的迁移文件示例:

# alembic/versions/20260326_add_user_avatar.py
"""add user avatar column

Revision ID: abc123
Revises: def456
Create Date: 2026-03-26 14:00:00.000000

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers
revision = 'abc123'
down_revision = 'def456'
branch_labels = None
depends_on = None

def upgrade() -> None:
    op.add_column('users', sa.Column('avatar', sa.String(length=500), nullable=True))

def downgrade() -> None:
    op.drop_column('users', 'avatar')

3.2 手动编写迁移

# 当 autogenerate 无法处理时,手动编写
def upgrade() -> None:
    # 添加列
    op.add_column("users", sa.Column("phone", sa.String(20)))

    # 重命名列
    op.alter_column("users", "name", new_column_name="display_name")

    # 创建索引
    op.create_index("ix_users_email", "users", ["email"], unique=True)

    # 创建外键
    op.create_foreign_key(
        "fk_posts_author", "posts", "users", ["author_id"], ["id"]
    )

def downgrade() -> None:
    op.drop_column("users", "phone")
    op.drop_index("ix_users_email", table_name="users")

3.3 应用迁移

# 查看迁移状态
alembic current
alembic history --verbose

# 升级到最新版本
alembic upgrade head

# 升级到指定版本
alembic upgrade abc123

# 升一级
alembic upgrade +1

# 回滚一级
alembic downgrade -1

# 回滚到初始版本
alembic downgrade base

4. 依赖链管理

4.1 迁移文件依赖关系

base (初始)
  ├── 001_add_users.py
  │        ↓
  ├── 002_add_posts.py
  │        ↓
  └── 003_add_comments.py

         head (最新)

每个迁移文件的 down_revision 必须指向前一个版本:

# 003_add_comments.py
down_revision = "002_add_posts"  # 指向 002

# 002_add_posts.py
down_revision = "001_add_users"  # 指向 001

# 001_add_users.py
down_revision = None  # 初始版本

4.2 合并分支(多条迁移链)

如果有两个开发分支合并:

# 迁移链 A: base → 001a → 002a
# 迁移链 B: base → 001b → 002b

# 合并后:
# base → 001a → 002a → 003_merge → 001b → 002b → head
# 或者合并到统一的主线

5. 多数据库配置

5.1 不同环境的数据库连接

# config.py
import os

class Settings:
    env = os.getenv("ENV", "development")

    databases = {
        "development": "postgresql+asyncpg://localhost:5432/myapp_dev",
        "testing": "postgresql+asyncpg://localhost:5432/myapp_test",
        "production": "postgresql+asyncpg://user:pass@db.example.com/myapp",
    }

    @property
    def database_url(self):
        return self.databases.get(self.env, self.databases["development"])
# 不同环境运行迁移
ENV=development alembic upgrade head
ENV=production alembic upgrade head

6. 最佳实践

✅ 推荐
1. 始终使用 --autogenerate + 手动审核结合
2. 每个迁移文件只做一件事
3. 始终编写 downgrade() 方法
4. 在 CI/CD 中先在测试库跑迁移,确认无误再上生产
5. 禁止在生产环境使用 alembic upgrade head,用 --sql 模式审核后再执行
6. 多人开发:每次拉代码后先 alembic upgrade head

❌ 避免
1. 不要手动修改已提交到 Git 的迁移文件
2. 不要在迁移中对数据进行不可逆操作
3. 不要跳过版本号

7. 小结

命令说明
alembic init alembic初始化
alembic revision -m "说明"创建空迁移
alembic revision --autogenerate -m "说明"自动生成迁移
alembic upgrade head升级到最新
alembic downgrade -1回滚一级
alembic history查看迁移历史
alembic current查看当前版本

💡 安全迁移原则:生产环境的每次迁移都应该先在本地测试库验证,生成 SQL 审核无误后再执行。DBA 审核 > 直接在线上执行。


🔗 扩展阅读