数据库迁移回滚
本文说明废弃功能分支时,如何回滚该分支已经执行过的 Alembic 数据库迁移。
适用场景
当某个功能分支已经执行过:
cd backend
uv run alembic upgrade heads但后续决定废弃该分支时,数据库可能停留在该分支新增的 migration 上。此时切回 main 前,应先把数据库回滚到主线 migration 基线。
推荐方式
使用脚本:
cd backend
# 只预览,不真正回滚
uv run python scripts/rollback_branch_migrations.py --base main
# 确认无误后执行回滚
uv run python scripts/rollback_branch_migrations.py --base main --execute脚本默认是 dry-run。只有显式传入 --execute 才会执行 alembic downgrade。
脚本做了什么
脚本会自动完成以下检查:
- 通过
git diff --name-status main...HEAD -- backend/alembic/versions找出当前分支新增的 migration 文件。 - 解析每个 migration 文件里的
revision和down_revision。 - 校验当前分支新增 migration 必须是一条线性的迁移链。
- 读取当前数据库的
alembic current。 - 只有当前数据库 revision 位于这条分支迁移链上时,才允许回滚。
- 自动计算回滚目标 revision,并在
--execute时执行 downgrade。
当前自动化分支示例
当前 automation-services 分支新增了两个 migration:
c9d0e1f2a3b4 -> d1e2f3a4b5c6 -> e2f4a6b8c0d1如果数据库当前是:
e2f4a6b8c0d1废弃该分支时,脚本会计算出回滚目标:
c9d0e1f2a3b4等价的手动命令是:
cd backend
uv run alembic downgrade c9d0e1f2a3b4但日常建议优先用脚本,因为脚本会先校验当前数据库 revision 是否真的属于当前分支新增 migration。
安全边界
脚本只处理“当前分支新增 migration 位于主线尾部”的常见场景。
脚本会拒绝以下情况:
- 当前分支修改或删除了已有 migration 文件,而不是只新增 migration。
- 当前分支新增 migration 形成多头或非线性链路。
- 当前数据库 revision 不在当前分支新增 migration 链上。
- 当前数据库存在多个 Alembic revision。
这些情况需要人工判断,不能一键回滚。
并行迁移与多 Head
不同功能分支可以从同一个 revision 独立创建迁移,合并后短期形成多个 head。部署和本地升级必须执行:
cd backend
uv run alembic upgrade heads该命令会执行版本图中的全部 head,不需要在运行时选择某个分支。CI 会在临时 PostgreSQL 中执行同一命令,以提前发现重复建表、重复字段等 SQL 冲突。PostgreSQL 在线迁移会通过 advisory lock 串行执行,避免多个 Backend 实例同时启动时竞争迁移。
多 head 只适用于彼此独立的迁移。如果一个迁移依赖另一个迁移,必须通过 down_revision 明确依赖关系。并行分支合并后,应在发布节点或后续迁移前提交 merge revision,使版本图重新收敛;不要在容器启动时动态生成 merge revision。
从 merge revision 选择性回滚模型选择功能
d0f4a6b8c2e5 同时合并了主分支上下文迁移和模型选择迁移。数据库位于该 revision 时,不能使用 alembic downgrade -1,因为 Alembic 无法判断要回退哪个父分支。
如果数据库当前 revision 正好是 d0f4a6b8c2e5,并且只需要回滚模型选择功能,应先退出 merge revision,再通过分支标签回退模型迁移:
cd backend
uv run alembic current
uv run alembic downgrade c9e3f5a7b2d4
uv run alembic downgrade scoped_model_selection@-1
uv run alembic current完成后 current revision 应为 c9e3f5a7b2d4。该流程会删除模型选择迁移新增的表和字段,但保留主分支的运行上下文迁移。不要把 scoped_model_selection@-1 替换为 f8c2a4d6e9b1-1;后者会沿两个分支一起回退,导致主分支功能丢失。
如果 current revision 已经晚于 d0f4a6b8c2e5,必须先逐项评估后续 migration,不能直接照搬上述命令。
注意事项
- 回滚 migration 会执行对应 migration 的
downgrade(),可能删除表、字段、索引和数据。 - 执行
--execute前,确认数据库是本地开发库或已经备份。 - 脚本不会删除 migration 文件,也不会切换 Git 分支。
- 回滚完成后再废弃分支或切回
main,可以避免主线代码无法识别当前数据库 revision。
构建与部署注意事项
脚本只回滚数据库,不会删除 backend/alembic/versions/*.py 文件。
因此,回滚后如果继续用当前废弃分支构建镜像,并且生产启动流程会执行:
alembic upgrade heads这些分支 migration 仍然会被再次执行。
正确流程是:
- 在本地或目标环境执行回滚脚本,把数据库 revision 降回主线基线。
- 切回
main,或切换到一个不包含废弃分支 migration 文件的分支。 - 确认代码中已不包含废弃分支的 migration 文件。
- 再构建镜像并部署。
数据库回滚和代码分支废弃是两个动作:脚本只负责数据库回滚;避免再次升级,必须保证部署产物不包含被废弃的 migration 文件。