Poco 使用手册
运维部署

数据库迁移回滚

本文说明废弃功能分支时,如何回滚该分支已经执行过的 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

脚本做了什么

脚本会自动完成以下检查:

  1. 通过 git diff --name-status main...HEAD -- backend/alembic/versions 找出当前分支新增的 migration 文件。
  2. 解析每个 migration 文件里的 revisiondown_revision
  3. 校验当前分支新增 migration 必须是一条线性的迁移链。
  4. 读取当前数据库的 alembic current
  5. 只有当前数据库 revision 位于这条分支迁移链上时,才允许回滚。
  6. 自动计算回滚目标 revision,并在 --execute 时执行 downgrade。

当前自动化分支示例

当前 automation-services 分支新增了两个 migration:

c9d0e1f2a3b4 -> d1e2f3a4b5c6 -> e2f4a6b8c0d1

如果数据库当前是:

e2f4a6b8c0d1

废弃该分支时,脚本会计算出回滚目标:

c9d0e1f2a3b4

等价的手动命令是:

cd backend
uv run alembic downgrade c9d0e1f2a3b4

但日常建议优先用脚本,因为脚本会先校验当前数据库 revision 是否真的属于当前分支新增 migration。

安全边界

脚本只处理“当前分支新增 migration 位于主线尾部”的常见场景。

脚本会拒绝以下情况:

  1. 当前分支修改或删除了已有 migration 文件,而不是只新增 migration。
  2. 当前分支新增 migration 形成多头或非线性链路。
  3. 当前数据库 revision 不在当前分支新增 migration 链上。
  4. 当前数据库存在多个 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,不能直接照搬上述命令。

注意事项

  1. 回滚 migration 会执行对应 migration 的 downgrade(),可能删除表、字段、索引和数据。
  2. 执行 --execute 前,确认数据库是本地开发库或已经备份。
  3. 脚本不会删除 migration 文件,也不会切换 Git 分支。
  4. 回滚完成后再废弃分支或切回 main,可以避免主线代码无法识别当前数据库 revision。

构建与部署注意事项

脚本只回滚数据库,不会删除 backend/alembic/versions/*.py 文件。

因此,回滚后如果继续用当前废弃分支构建镜像,并且生产启动流程会执行:

alembic upgrade heads

这些分支 migration 仍然会被再次执行。

正确流程是:

  1. 在本地或目标环境执行回滚脚本,把数据库 revision 降回主线基线。
  2. 切回 main,或切换到一个不包含废弃分支 migration 文件的分支。
  3. 确认代码中已不包含废弃分支的 migration 文件。
  4. 再构建镜像并部署。

数据库回滚和代码分支废弃是两个动作:脚本只负责数据库回滚;避免再次升级,必须保证部署产物不包含被废弃的 migration 文件。

On this page