前置知识: Python

Python与数据库迁移

00:00
7 min Intermediate 2026/6/14

Alembic与数据库迁移

什么是数据库迁移

数据库迁移是指对数据库结构进行版本化管理的过程。当你修改了数据表的结构(比如新增一列、删除一张表、修改字段类型),你需要一种可靠的方式把这些变更记录下来,并能在不同环境(开发、测试、生产)中重复执行。

如果没有迁移工具,你可能会手动去数据库里执行 SQL 语句来改表结构。但这样做有几个严重问题:你无法追踪改了什么、无法回滚、无法在团队之间同步变更。数据库迁移工具就是为了解决这些问题而诞生的。

在 Python 生态中,Alembic 是最主流的数据库迁移工具,它与 SQLAlchemy ORM 紧密配合,是 Python Web 开发中不可或缺的一环。

基础概念

迁移版本链

Alembic 把每一次数据库结构变更称为一个”迁移”(migration)。每个迁移都有一个唯一的版本标识,多个迁移按顺序组成一条版本链。数据库中会有一张特殊的表(默认叫 alembic_version)来记录当前数据库处于哪个版本。

自动生成与手写迁移

Alembic 支持两种方式创建迁移:

  • 自动生成(autogenerate):Alembic 对比你的 SQLAlchemy 模型定义和数据库实际结构,自动生成迁移脚本
  • 手写迁移:你手动编写迁移脚本中的 upgrade 和 downgrade 函数

自动生成非常方便,但并非万能。复杂变更(如数据迁移、列重命名)仍需手动调整。

upgrade 与 downgrade

每个迁移脚本都包含两个函数:

  • upgrade():执行升级操作,把数据库结构从上一个版本变更为当前版本
  • downgrade():执行回退操作,把数据库结构从当前版本恢复为上一个版本

这两个函数必须互为逆操作,否则回滚会出问题。

快速上手

安装 Alembic

# 安装 alembic 和 sqlalchemy
pip install alembic sqlalchemy

初始化 Alembic

在你的项目根目录下执行初始化命令:

# 创建 alembic 配置目录和文件
alembic init migrations

执行后会在当前目录生成以下文件结构:

alembic.ini          # Alembic 配置文件
migrations/
  env.py             # 迁移运行环境配置
  script.py.mako     # 迁移脚本模板
  versions/          # 存放迁移脚本的目录(初始为空)

配置数据库连接

打开 alembic.ini,修改 sqlalchemy.url 为你的数据库连接地址:

# 修改这一行为你的数据库地址
sqlalchemy.url = sqlite:///myapp.db

如果你使用 PostgreSQL,连接字符串类似:

sqlalchemy.url = postgresql://user:password@localhost:5432/mydb

创建第一个迁移

# 根据模型自动生成迁移脚本
alembic revision --autogenerate -m "add users table"

这会在 migrations/versions/ 目录下生成一个 Python 文件,文件名类似 a1b2c3d4e5f6_add_users_table.py

执行迁移

# 升级到最新版本
alembic upgrade head

回退迁移

# 回退一个版本
alembic downgrade -1

# 回退到指定版本
alembic downgrade a1b2c3d4e5f6

# 回退所有迁移(回到空白数据库状态)
alembic downgrade base

详细用法

配置 env.py 关联 SQLAlchemy 模型

要让 autogenerate 功能正常工作,需要在 env.py 中告诉 Alembic 你的模型定义在哪里。打开 migrations/env.py,找到 target_metadata 变量,修改为:

# 导入你的 SQLAlchemy Base
from myapp.models import Base

# 将 target_metadata 指向你的模型元数据
target_metadata = Base.metadata

这样 Alembic 在自动生成迁移时,就能发现你定义的所有模型类,并与数据库实际结构进行对比。

编写 SQLAlchemy 模型

# myapp/models.py
from sqlalchemy import String, Integer
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

# 声明基类
class Base(DeclarativeBase):
    pass

# 定义用户模型
class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(100), nullable=False)
    email: Mapped[str] = mapped_column(String(200), unique=True)
    age: Mapped[int] = mapped_column(Integer, nullable=True)

自动生成迁移脚本

当你修改了模型后(比如新增了字段或新增了模型类),运行:

alembic revision --autogenerate -m "add age column to users"

生成的迁移脚本大致如下:

"""add age column to users

Revision ID: b2c3d4e5f6a7
Revises: a1b2c3d4e5f6
Create Date: 2026-01-15 10:30:00.000000
"""
from alembic import op
import sqlalchemy as sa

# 版本标识
revision = 'b2c3d4e5f6a7'
down_revision = 'a1b2c3d4e5f6'
branch_labels = None
depends_on = None

def upgrade() -> None:
    # 升级操作:添加 age 列
    op.add_column('users', sa.Column('age', sa.Integer(), nullable=True))

def downgrade() -> None:
    # 回退操作:删除 age 列
    op.drop_column('users', 'age')

手动编写迁移脚本

有时自动生成无法满足需求,你需要手动创建迁移:

# 创建空白的迁移脚本
alembic revision -m "rename column name to full_name"

然后手动编辑生成的文件:

"""rename column name to full_name

Revision ID: c3d4e5f6a7b8
Revises: b2c3d4e5f6a7
Create Date: 2026-01-15 11:00:00.000000
"""
from alembic import op

revision = 'c3d4e5f6a7b8'
down_revision = 'b2c3d4e5f6a7'
branch_labels = None
depends_on = None

def upgrade() -> None:
    # 重命名列(alembic 的 batch 模式兼容 SQLite)
    with op.batch_alter_table('users') as batch_op:
        batch_op.alter_column('name', new_column_name='full_name')

def downgrade() -> None:
    # 回退:把列名改回去
    with op.batch_alter_table('users') as batch_op:
        batch_op.alter_column('full_name', new_column_name='name')

常用的 op 操作

Alembic 通过 op 对象提供了丰富的数据库操作方法:

# 创建新表
def upgrade() -> None:
    op.create_table(
        'articles',
        sa.Column('id', sa.Integer(), primary_key=True),
        sa.Column('title', sa.String(200), nullable=False),
        sa.Column('content', sa.Text()),
        sa.Column('author_id', sa.Integer(), sa.ForeignKey('users.id')),
    )

# 添加列
op.add_column('users', sa.Column('phone', sa.String(20)))

# 删除列
op.drop_column('users', 'phone')

# 修改列类型
op.alter_column('users', 'age',
                existing_type=sa.Integer(),
                type_=sa.String(10))

# 添加唯一约束
op.create_unique_constraint('uq_users_email', 'users', ['email'])

# 添加外键
op.create_foreign_key('fk_articles_author', 'articles', 'users', ['author_id'], ['id'])

# 创建索引
op.create_index('ix_users_name', 'users', ['name'])

# 删除表
op.drop_table('articles')

查看迁移状态

# 查看当前数据库的迁移版本
alembic current

# 查看迁移历史
alembic history

# 查看详细历史(显示每个迁移的具体信息)
alembic history --verbose

升级和降级的多种方式

# 升级到最新版本
alembic upgrade head

# 升级到指定版本
alembic upgrade a1b2c3d4e5f6

# 相对升级(向前推进2个版本)
alembic upgrade +2

# 降级一个版本
alembic downgrade -1

# 降级到指定版本
alembic downgrade a1b2c3d4e5f6

# 降级到最初状态
alembic downgrade base

# 升级到某个相对位置
alembic upgrade head+1

数据迁移

有时候你不仅需要修改表结构,还需要迁移数据。这必须在迁移脚本中手动完成:

from alembic import op
import sqlalchemy as sa

def upgrade() -> None:
    # 先添加新列
    op.add_column('users', sa.Column('full_name', sa.String(200)))

    # 创建临时连接来执行数据迁移
    user_table = sa.table('users',
        sa.column('name', sa.String),
        sa.column('full_name', sa.String),
    )

    # 把 name 列的值复制到 full_name 列
    op.execute(
        user_table.update().values(full_name=user_table.c.name)
    )

    # 最后删除旧列
    op.drop_column('users', 'name')

def downgrade() -> None:
    # 回退操作
    op.add_column('users', sa.Column('name', sa.String(200)))
    # 反向复制数据
    user_table = sa.table('users',
        sa.column('name', sa.String),
        sa.column('full_name', sa.String),
    )
    op.execute(
        user_table.update().values(name=user_table.c.full_name)
    )
    op.drop_column('users', 'full_name')

常见场景

在 FastAPI 项目中集成 Alembic

一个典型的 FastAPI 项目结构如下:

myapp/
  main.py           # FastAPI 应用入口
  models.py         # SQLAlchemy 模型定义
  database.py       # 数据库连接配置
  alembic.ini       # Alembic 配置
  migrations/
    env.py
    versions/

在 database.py 中定义数据库连接:

# myapp/database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = "sqlite:///./myapp.db"

engine = create_engine(DATABASE_URL, echo=True)
SessionLocal = sessionmaker(bind=engine)

class Base(DeclarativeBase):
    pass

在 env.py 中引入 Base:

# 修改 migrations/env.py 中的关键部分
from myapp.database import Base
from myapp.models import *  # 确保所有模型都被导入

target_metadata = Base.metadata

在生产环境中安全执行迁移

# 先查看当前状态
alembic current

# 检查是否有待执行的迁移(不实际执行)
alembic upgrade head --sql > migration.sql

# 确认 SQL 无误后再实际执行
alembic upgrade head

多分支迁移合并

当多个开发者同时创建迁移时,可能会产生分支。Alembic 提供了合并功能:

# 合并多个分支
alembic merge -m "merge branches" a1b2c3d4 e5f6a7b8

# 查看是否有分支
alembic heads

注意事项与常见错误

autogenerate 不是万能的

Alembic 的自动生成功能无法检测以下变更:

  • 列的重命名(会被识别为删除旧列并添加新列,导致数据丢失)
  • 重命名
  • 数据迁移
  • 约束的修改

遇到这些情况,必须手动编写迁移脚本。

SQLite 的限制

SQLite 不支持直接修改列定义删除列。Alembic 提供了 batch 模式来兼容 SQLite:

# 在 env.py 中启用 batch 模式
def run_migrations_online():
    connectable = engine
    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
            render_as_batch=True,  # 启用 batch 模式
        )
        with context.begin_transaction():
            context.run_migrations()

迁移脚本中使用 batch_alter_table:

def upgrade() -> None:
    with op.batch_alter_table('users') as batch_op:
        batch_op.alter_column('name', nullable=False)

不要修改已执行的迁移脚本

一旦某个迁移脚本已经在数据库执行过,就不要再修改它。否则会导致版本链不一致。如果发现迁移有错误,应该创建一个新的迁移修正

始终检查自动生成的迁移

自动生成迁移脚本不一定完全正确,在执行前务必检查 upgrade 和 downgrade 函数内容确认操作符合预期。

downgrade 必须是 upgrade 的逆操作

如果 downgrade 函数不能正确回退 upgrade 的操作,那么回滚数据库结构就会出错。编写迁移时一定要确保两者互为逆操作

进阶用法

自定义迁移模板

你可以修改 migrations/script.py.mako 来自定义迁移脚本生成模板:

"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}

# revision identifiers
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}

def upgrade() -> None:
    ${upgrades if upgrades else "pass"}

def downgrade() -> None:
    ${downgrades if downgrades else "pass"}

在代码中执行迁移

有时候你需要在应用启动时自动执行迁移,而不是手动运命令:

from alembic.config import Config
from alembic import command

def run_migrations():
    # 创建 Alembic 配置
    alembic_cfg = Config("alembic.ini")
    # 执行升级
    command.upgrade(alembic_cfg, "head")

# 在应用启动时调用
if __name__ == "__main__":
    run_migrations()

使用环境变量管理数据库连接

不要把数据库密码硬编码在 alembic.ini 中,应该通过环境变量读取:

# 在 env.py 中动态设置数据库连接
import os

# 从环境变量获取数据库连接字符串
database_url = os.getenv("DATABASE_URL", "sqlite:///default.db")

def run_migrations_online():
    configuration = config.get_section(config.config_ini_section)
    configuration["sqlalchemy.url"] = database_url
    connectable = engine_from_config(
        configuration,
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )
    # ... 后续代码不变

离线模式生成 SQL

在不连接数据的情况下生成 SQL 脚本,适合交给 DBA 审

# 生成 SQL 而不实际执行
alembic upgrade head --sql

# 生成从某个版本到另一个版本的 SQL
alembic upgrade a1b2c3:e5f6a7b --sql

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式