《Python编程实战》6.3 Alembic 迁移与数据演进

Alembic 1.20.0 实战:从 init 到 env.py 配置、autogenerate 生成迁移、upgrade/downgrade/history/current 全套命令,讲清加列、数据回填与不可逆操作等演进策略,代码在 SQLite 上真跑过。

本节目标:用 Alembic 把数据库结构变更写成有版本号、可回滚、可多人协作的迁移脚本,掌握 autogenerate 与全套升降级命令,并想清演进策略。
适用版本:Python 3.12+(实测 3.14.6);SQLAlchemy 2.1.4、Alembic 1.20.0

6.3 Alembic 迁移与数据演进

6.1 节的 Base.metadata.create_all() 只能建表,改不了表。真实项目里加一列、改一次类型、回填一批数据,都需要在不丢数据、可回退、可复现的前提下完成。Alembic 就是数据库结构的 Git:每次变更是一个带 revision 编号的脚本,按链式顺序应用或回退。

本机实测版本 Alembic 1.20.0,数据库用 SQLite。无 PostgreSQL 服务端,PG 专属方言相关部分会显式标注未实测。

6.3.1 初始化项目

在项目根目录执行 alembic init,它会生成目录骨架:

alembic init -t generic migrations

生成的结构:

migrations/
  env.py            # 运行时配置:连数据库、指定 metadata
  script.py.mako    # 迁移脚本模板
  versions/         # 每个迁移脚本一个文件,按 revision 链
alembic.ini         # 全局配置,含 sqlalchemy.url

-t generic 是不绑定具体框架的通用模板;若用 FastAPI 项目也推荐它,保持与框架解耦。

6.3.2 配置 env.py

关键改动是让 Alembic 知道「谁是要迁移的目标」。把 target_metadata 指向你的 Base.metadata:

# migrations/env.py
from logging.config import fileConfig
from sqlalchemy import engine_from_config, pool
from alembic import context
from app_models import Base  # 你的模型基类

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

target_metadata = Base.metadata


def run_migrations_offline() -> None:
    url = config.get_main_option("sqlalchemy.url")
    context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
    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()

要点:

  • target_metadata = Base.metadata 是 autogenerate 的前提,指错了会生成空迁移或误删表。
  • poolclass=pool.NullPool 是 Alembic 官方模板的默认,迁移是一次性短连接,不需要池。
  • sqlalchemy.url 可以写在 alembic.ini,也可以用环境变量覆盖,避免把生产密码提交进仓库。

6.3.3 autogenerate 生成迁移

模型里定义一个 User 后,让 Alembic 对比模型与数据库的差异:

alembic revision --autogenerate -m "create user"

实测输出(关键行):

INFO  [alembic.autogenerate.compare.tables] Detected added table 'user'
Generating .../versions/33415f0a9199_create_user.py ...  done

生成的脚本长这样:

"""create user

Revision ID: 33415f0a9199
Revises:
Create Date: 2026-10-09 ...
"""
from alembic import op
import sqlalchemy as sa

revision: str = '33415f0a9199'
down_revision = None


def upgrade() -> None:
    op.create_table('user',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('name', sa.String(), nullable=False),
        sa.PrimaryKeyConstraint('id'),
    )


def downgrade() -> None:
    op.drop_table('user')

--autogenerate 只是给你一份草稿,务必逐行 review:它擅长加表加列,但对改类型、重命名、数据回填无能为力,且可能漏掉服务端默认值。草稿里的 # ### commands auto generated ### 注释就是提醒你「这里需要人工确认」。

6.3.4 应用、回退与查看

核心命令就四条:

命令作用
alembic upgrade head应用所有未执行的迁移到最新
alembic upgrade +1 / alembic downgrade -1前进 / 回退一步
alembic current显示数据库当前 revision
alembic history列出迁移链

实测应用首个迁移:

INFO  [alembic.runtime.migration] Running upgrade  -> 33415f0a9199, create user

执行后数据库里出现了两张表——业务表加一张 alembic_version(记录当前 revision):

['alembic_version', 'user']

6.3.5 演进:加一列再验证

给 User 加一个可空的 email 列,再生成一次迁移:

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    email: Mapped[str | None]
alembic revision --autogenerate -m "add email"
alembic upgrade head
alembic history
alembic current

实测输出:

INFO  [alembic.autogenerate.compare.tables] Detected added column 'user.email'
INFO  [alembic.runtime.migration] Running upgrade 33415f0a9199 -> 498b5ecc19de, add email
33415f0a9199 -> 498b5ecc19de (head), add email
<base> -> 33415f0a9199, create user
498b5ecc19de (head)

最终表结构(SQLite 实测):

CREATE TABLE user (
	id INTEGER NOT NULL,
	name VARCHAR NOT NULL, email VARCHAR,
	PRIMARY KEY (id)
)

history 是从新到旧列出的链,current 显示数据库停在 498b5ecc19de (head),与模型一致。

6.3.6 演进策略

迁移是不可逆操作的重灾区,策略比命令更重要:

  • 加列一律可空或带默认值:ADD COLUMN ... NOT NULL 在已有数据的表上会失败;先加可空列,回填后再收紧。
  • 改类型/重命名要手写:autogenerate 常把「重命名」误判成「删一列加一列」,会丢数据。用 op.alter_column / op.rename_table 显式表达。
  • 数据回填单独写迁移:在 upgrade() 里用 op.execute("UPDATE ...") 或 op.get_bind() 执行,别混在结构变更里。
  • 大表分批:给千万行表加索引或回填时,考虑 CREATE INDEX CONCURRENTLY(PostgreSQL)等在线操作,避免长时间锁表。
  • 一次迁移只做一件事:便于回滚和定位;把「建表」和「回填」拆成两个 revision。
  • 先降级演练再上线:在预发环境 upgrade 后 downgrade -1 再 upgrade,确认双向可用。

6.3.7 与部署流水线的衔接

生产环境应用迁移的常见形态:

# 发布前,在一次性容器里执行(不要放进应用启动流程并发跑)
alembic upgrade head

为什么强调「不要放进应用启动」:多实例同时启动时,upgrade head 会并发争抢,可能两个进程同时跑同一个迁移。正确做法是把它作为独立的发布步骤(如 Kubernetes 的 initContainer 或 CI 的一个 job),执行一次即可。

如果确实要在代码里调用(例如测试 fixture),用编程式 API:

from alembic import command
from alembic.config import Config

cfg = Config("alembic.ini")
command.upgrade(cfg, "head")

测试里也可以用 command.downgrade(cfg, "base") 清库,保证每个用例从干净 schema 起步。

6.3.8 PostgreSQL 差异(本机未实测)

SQLite 不支持 ALTER COLUMN 的部分能力,且默认「非事务性 DDL」。换到 PostgreSQL 时要注意:

  • PostgreSQL 支持事务性 DDL,迁移失败可整体回滚,比 SQLite 安全。
  • 枚举类型需先 CREATE TYPE,删除时要单独 DROP TYPE。
  • ALTER TABLE ... ALTER COLUMN ... TYPE 在大表上可能重写全表,要评估锁窗口。
  • 方言专属的 JSONB、SERIAL、ON CONFLICT 等,本机无 PG 服务端,未实测,正文不给出运行结果。

延伸阅读:Python 数据库与 ORM 完全指南 的迁移章节有更多方言对照。

小结

  • Alembic 是数据库结构的版本控制:alembic init -t generic migrations 建骨架,env.py 里把 target_metadata 指向 Base.metadata。
  • alembic revision --autogenerate -m "..." 生成迁移草稿,但必须人工 review,它处理不了改类型、重命名和数据回填。
  • 四条核心命令:upgrade / downgrade / current / history;Alembic 用一张 alembic_version 表记录当前 revision。
  • 加列要可空或带默认值;改类型、重命名、回填都手写;一次迁移只做一件事。
  • 生产把 alembic upgrade head 当独立发布步骤执行一次,不要塞进应用启动流程并发跑。
  • PostgreSQL 的事务性 DDL 比 SQLite 更安全,但方言专属特性本机未实测,正文已标注。

第 6 章到此收口:6.1 用类型化模型把表映射成对象,6.2 把 session、事务、连接池和 N+1 这些运行时问题讲透,6.3 用 Alembic 让结构变更可追溯可回滚。数据访问层稳了,接下来 7.1 节转向接口契约——用 Pydantic V2 设计 API 模型,把本节讲的 ORM 模型与对外契约彻底分开。

阅读导航:上一节:事务、连接池与 N+1 治理 · 下一节:Pydantic V2 模型设计 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「python」更多文章

  1. 《Python高级编程》目录
  2. 《Python高级编程》11.3 PEP 流程与版本迁移策略
  3. 《Python高级编程》11.2 嵌入式与自由线程运行时