You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
14 KiB
14 KiB
FastAPI + Alembic 迁移管理最佳实践(AI 编辑器时代)
问题背景
在使用 AI 编辑器(Cursor、Copilot、Claude)开发时,常见问题:
典型场景
1. AI 自动修改 Model 字段
2. 开发者忘记创建迁移文件
3. 多次修改累积后一次性创建迁移
4. 迁移文件冲突或断链
5. 团队成员迁移版本不一致
后果
- ❌ 迁移链断裂
- ❌ 数据库与 Model 不同步
- ❌ 团队成员环境不一致
- ❌ 生产部署失败
核心原则
1. Model First,Migration Immediately
改 Model → 立即创建迁移 → 测试 → 提交
2. Never Skip Migration
# ❌ 错误做法
# 1. 修改 Model
# 2. 直接运行应用(SQLModel 自动同步)
# 3. 数据库已变但无迁移文件
# ✅ 正确做法
# 1. 修改 Model
# 2. alembic revision --autogenerate -m "描述"
# 3. 检查并调整迁移文件
# 4. alembic upgrade head
# 5. 测试
# 6. 提交代码(Model + 迁移文件)
3. Atomic Commits
# 每次提交必须包含
git add server/app/models/xxx.py
git add server/alembic/versions/xxx_description.py
git commit -m "feat: 添加 XXX 字段"
工作流规范
🔄 标准迁移工作流
第一步:修改 Model
# server/app/models/user.py
class User(SQLModel, table=True):
# ... 现有字段
# ✅ 新增字段(AI 建议或手动添加)
avatar_url: str | None = Field(default=None, max_length=500)
第二步:立即创建迁移
# 进入服务器目录
cd server
# 自动生成迁移文件
docker exec jointo-server-app alembic revision --autogenerate -m "add user avatar_url field"
# 输出示例:
# Generating /app/alembic/versions/20260215_1630_abc123def456_add_user_avatar_url_field.py ... done
第三步:检查迁移文件
# ⚠️ 必须手动检查 AI 生成的迁移是否正确
# 检查点:
# 1. 是否使用了 IF NOT EXISTS(幂等性)
# 2. 是否正确处理了默认值
# 3. 是否需要数据迁移逻辑
# 4. 索引是否正确创建
第四步:应用迁移
# 在开发环境测试
docker exec jointo-server-app alembic upgrade head
# 检查结果
docker exec jointo-server-postgres psql -U jointoAI -d jointo -c "\d users"
第五步:回滚测试
# 测试 downgrade 是否可用
docker exec jointo-server-app alembic downgrade -1
docker exec jointo-server-app alembic upgrade head
第六步:提交代码
git add server/app/models/user.py
git add server/alembic/versions/20260215_1630_abc123def456_add_user_avatar_url_field.py
git commit -m "feat(user): add avatar_url field for user profile"
git push origin feature/user-avatar
防御性迁移编写
1. 始终使用幂等操作
✅ 添加列(幂等)
def upgrade() -> None:
"""添加 avatar_url 字段"""
op.execute("""
ALTER TABLE users
ADD COLUMN IF NOT EXISTS avatar_url VARCHAR(500);
""")
op.execute("""
COMMENT ON COLUMN users.avatar_url IS '用户头像URL';
""")
✅ 创建索引(幂等)
def upgrade() -> None:
"""为 email 创建唯一索引"""
op.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email
ON users (email);
""")
✅ 创建函数(幂等)
def upgrade() -> None:
"""创建触发器函数"""
# 先删除旧的(包括依赖的触发器)
op.execute("DROP FUNCTION IF EXISTS update_timestamp() CASCADE;")
# 再创建新的
op.execute("""
CREATE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
""")
2. 数据迁移模板
添加非空字段(带默认值)
def upgrade() -> None:
"""添加非空字段 status"""
# Step 1: 添加可空列
op.execute("""
ALTER TABLE projects
ADD COLUMN IF NOT EXISTS status SMALLINT;
""")
# Step 2: 填充默认值
op.execute("""
UPDATE projects
SET status = 1
WHERE status IS NULL;
""")
# Step 3: 设置为非空
op.execute("""
ALTER TABLE projects
ALTER COLUMN status SET NOT NULL;
""")
# Step 4: 设置默认值
op.execute("""
ALTER TABLE projects
ALTER COLUMN status SET DEFAULT 1;
""")
重命名列(保留旧数据)
def upgrade() -> None:
"""重命名 metadata -> meta_data"""
# 检查列是否存在
op.execute("""
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'projects' AND column_name = 'metadata'
) THEN
ALTER TABLE projects RENAME COLUMN metadata TO meta_data;
END IF;
END $$;
""")
AI 编辑器使用规范
Cursor 配置建议
1. 创建 .cursor/rules/alembic.md
# Alembic 迁移规则
## 严格禁止
1. ❌ 禁止直接修改已提交的迁移文件
2. ❌ 禁止跳过迁移文件创建
3. ❌ 禁止在 SQLModel 中使用 `create_all()`(开发环境除外)
## 必须遵守
1. ✅ 修改 Model 后必须立即创建迁移
2. ✅ 所有 DDL 操作必须使用 `IF NOT EXISTS`
3. ✅ 函数创建必须先 DROP CASCADE
## 迁移文件模板
\`\`\`python
def upgrade() -> None:
"""描述变更内容"""
# 使用原生 SQL 确保幂等性
op.execute(\"\"\"
ALTER TABLE xxx
ADD COLUMN IF NOT EXISTS yyy TYPE;
\"\"\")
def downgrade() -> None:
"""回滚操作"""
op.execute(\"\"\"
ALTER TABLE xxx
DROP COLUMN IF EXISTS yyy;
\"\"\")
\`\`\`
2. 项目级提示词
# .cursorrules 或 CLAUDE.md
## 数据库迁移要求
当修改任何 SQLModel 模型时:
1. 生成迁移文件:`alembic revision --autogenerate -m "描述"`
2. 使用幂等 SQL:`IF NOT EXISTS`, `IF EXISTS`
3. 函数创建:先 `DROP IF EXISTS ... CASCADE`
4. 提供回滚逻辑:实现 `downgrade()`
5. 包含注释:`COMMENT ON COLUMN ...`
Copilot / Claude 提示词
当我修改 SQLModel 时:
1. 提醒我创建 Alembic 迁移
2. 生成幂等的迁移脚本
3. 包含 upgrade 和 downgrade
4. 使用原生 SQL 而非 Alembic ORM API
迁移冲突解决
场景 1:分支合并冲突
# 问题:两个分支都创建了迁移
main: 20260215_1000_aaa -> 20260215_1100_bbb
feature: 20260215_1000_aaa -> 20260215_1050_ccc
# 解决方案
# 1. 切换到 feature 分支
git checkout feature/xxx
# 2. 拉取最新 main
git fetch origin main
# 3. 检查冲突
alembic history
# 4. 调整 down_revision
# 编辑 20260215_1050_ccc.py
down_revision = '20260215_1100_bbb' # 改为指向最新的 main 迁移
# 5. 测试迁移链
alembic upgrade head
# 6. 提交合并
git add server/alembic/versions/20260215_1050_ccc.py
git commit -m "fix: resolve migration conflict"
场景 2:遗漏的迁移
# 问题:Model 已改但忘记创建迁移
# 诊断
alembic check # 查看是否有未迁移的变更
# 解决
# 1. 创建迁移
alembic revision --autogenerate -m "补充遗漏的迁移"
# 2. 检查生成的迁移是否正确
cat alembic/versions/xxx_missing_changes.py
# 3. 应用
alembic upgrade head
场景 3:生产环境与开发环境不一致
# 问题:生产环境缺少某些迁移
# 1. 检查生产环境版本
alembic current
# 2. 查看缺失的迁移
alembic history
# 3. 逐步应用(避免一次性 upgrade head)
alembic upgrade +1 # 应用下一个
alembic upgrade +1 # 再应用一个
# ... 直到同步
# 4. 或直接升级到最新(风险较高)
alembic upgrade head
团队协作规范
1. 迁移文件命名约定
# 格式:YYYYMMDD_HHMM_<hash>_<description>.py
20260215_1630_abc123def456_add_user_avatar_field.py
# 描述应清晰
✅ add_user_avatar_field
✅ fix_project_status_enum
✅ create_storyboard_tables
❌ update_models
❌ fix_bug
❌ temp_changes
2. Pull Request 检查清单
## 迁移检查清单
- [ ] Model 变更与迁移文件一致
- [ ] 迁移文件使用幂等操作
- [ ] 包含 upgrade 和 downgrade
- [ ] 本地测试通过
- [ ] 回滚测试通过
- [ ] 迁移链无断裂(alembic history)
- [ ] 生产环境部署计划已准备
3. Code Review 重点
# Reviewer 检查点:
# ✅ 1. 幂等性
ALTER TABLE users ADD COLUMN IF NOT EXISTS ...
# ✅ 2. 数据安全
# 添加非空列时先允许 NULL,填充数据后再改为 NOT NULL
# ✅ 3. 性能影响
# 大表添加索引时考虑使用 CONCURRENTLY(PostgreSQL)
CREATE INDEX CONCURRENTLY idx_name ON table(col);
# ✅ 4. 回滚可行性
# downgrade 能否安全执行?是否会丢失数据?
# ✅ 5. 函数/触发器
# 必须先 DROP CASCADE 再 CREATE
自动化工具
1. Pre-commit Hook
创建 .git/hooks/pre-commit:
#!/bin/bash
# 检查是否修改了 models 但没有迁移文件
if git diff --cached --name-only | grep -q "server/app/models/"; then
if ! git diff --cached --name-only | grep -q "server/alembic/versions/"; then
echo "⚠️ 检测到 Model 变更但未包含迁移文件!"
echo ""
echo "请执行:"
echo " cd server"
echo " alembic revision --autogenerate -m '描述变更'"
echo ""
echo "如果确认无需迁移,使用 --no-verify 跳过检查"
exit 1
fi
fi
2. 迁移验证脚本
创建 server/scripts/validate_migrations.py:
#!/usr/bin/env python3
"""
验证迁移链完整性
"""
import subprocess
import sys
def check_migration_chain():
"""检查迁移链是否完整"""
result = subprocess.run(
['alembic', 'history'],
capture_output=True,
text=True
)
if result.returncode != 0:
print("❌ 迁移链检查失败")
print(result.stderr)
return False
print("✅ 迁移链完整")
return True
def check_pending_changes():
"""检查是否有未迁移的变更"""
result = subprocess.run(
['alembic', 'check'],
capture_output=True,
text=True
)
if "No new upgrade operations detected" not in result.stdout:
print("⚠️ 检测到未迁移的变更!")
print(result.stdout)
return False
print("✅ 无待迁移变更")
return True
if __name__ == '__main__':
success = check_migration_chain() and check_pending_changes()
sys.exit(0 if success else 1)
3. CI/CD 集成
.github/workflows/validate-migrations.yml 或 .gitea/workflows/validate.yml:
name: Validate Migrations
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install dependencies
run: |
cd server
pip install -r requirements.txt
- name: Check migration chain
run: |
cd server
alembic history
- name: Check for pending changes
run: |
cd server
alembic check
常见错误及解决方案
错误 1:Multiple head revisions
# 错误信息
alembic.util.exc.CommandError: Multiple head revisions are present
# 原因:两个分支创建了并行的迁移
# 解决:合并分支
alembic merge <rev1> <rev2> -m "merge branches"
错误 2:Can't locate revision
# 错误信息
Can't locate revision identified by 'abc123'
# 原因:缺少中间迁移文件
# 解决:
# 1. 从其他分支或团队成员获取缺失的迁移文件
# 2. 或重置迁移历史(慎用)
错误 3:Target database is not up to date
# 错误信息
Target database is not up to date
# 原因:数据库版本落后于代码
# 解决:
alembic upgrade head
最佳实践总结
✅ DO(应该做)
- 一次变更一个迁移:每次 Model 改动立即创建迁移
- 使用幂等操作:
IF NOT EXISTS,IF EXISTS - 原生 SQL 优先:避免 Alembic ORM API
- 测试回滚:确保 downgrade 可用
- 团队同步:频繁拉取最新迁移
- 命名清晰:迁移描述应准确反映变更
- 代码审查:迁移文件必须 Review
❌ DON'T(不应该做)
- 修改已提交的迁移:已合并的迁移文件禁止修改
- 跳过迁移:不要直接改数据库
- 手动删除迁移:不要随意删除迁移文件
- 忽略冲突:合并冲突必须解决
- 生产环境直接改:所有变更必须通过迁移
- 多个 head:避免创建并行迁移分支
快速参考
常用命令
# 创建迁移
alembic revision --autogenerate -m "description"
# 应用迁移
alembic upgrade head
alembic upgrade +1 # 应用下一个
alembic upgrade <revision> # 应用到指定版本
# 回滚迁移
alembic downgrade -1 # 回滚一个
alembic downgrade <revision> # 回滚到指定版本
# 查看状态
alembic current # 当前版本
alembic history # 迁移历史
alembic check # 检查未迁移变更
# 合并分支
alembic merge <rev1> <rev2> -m "merge"
紧急恢复
# 数据库完全混乱时
# 1. 备份数据库
pg_dump -U user -d db > backup.sql
# 2. 删除 alembic_version 表
DROP TABLE alembic_version;
# 3. 重新初始化
alembic stamp head
# 4. 验证
alembic current
总结
在 AI 编辑器时代,迁移管理的关键是:
- 🚦 规范化:建立严格的工作流
- 🛡️ 防御性:所有操作都幂等
- 🔄 自动化:Pre-commit + CI/CD
- 👥 团队化:Code Review + 文档
核心原则:Model 变更必须伴随迁移,迁移必须幂等且可回滚!
文档版本: v1.0
更新日期: 2026-02-15
适用项目: FastAPI + Alembic + SQLModel + PostgreSQL