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.
7.6 KiB
7.6 KiB
Alembic 迁移系统实施
日期: 2026-01-27
类型: 基础设施改进
影响范围: 数据库迁移工具
概述
将项目的数据库迁移系统从自定义脚本迁移到 Alembic,提供更强大的版本管理、自动生成和回滚功能。
变更内容
1. 新增文件
配置文件
server/alembic.ini- Alembic 主配置文件server/alembic/env.py- 环境配置,支持异步数据库server/alembic/script.py.mako- 迁移文件模板server/alembic/versions/.gitkeep- 版本目录占位
工具脚本
server/scripts/db_migrate.py- 便捷迁移命令封装
文档
docs/server/guides/alembic-migration-guide.md- 完整使用指南
2. 修改文件
迁移脚本
server/migrate_db.sh- 更新为使用 Alembic
技术栈文档
.claude/skills/jointo-tech-stack/references/database.md- 更新迁移管理部分
3. 保留文件
server/app/migrations/- 旧迁移文件保留作为参考server/run_migration.py- 保留但标记为 deprecated
功能特性
1. 自动生成迁移
# 自动检测模型变更并生成迁移脚本
python scripts/db_migrate.py create "描述" --autogenerate
2. 版本管理
- 基于 Git 风格的版本链
- 支持分支合并
- 自动依赖管理
3. 回滚支持
# 回滚一个版本
python scripts/db_migrate.py downgrade
# 回滚到指定版本
python scripts/db_migrate.py downgrade <版本号>
# 回滚所有
python scripts/db_migrate.py downgrade base
4. 状态查询
# 当前版本
python scripts/db_migrate.py current
# 迁移历史
python scripts/db_migrate.py history
# 最新版本
python scripts/db_migrate.py heads
技术实现
1. 异步支持
alembic/env.py 配置了异步数据库引擎:
async def run_async_migrations() -> None:
"""在线模式运行异步迁移"""
connectable = engine
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
await connectable.dispose()
2. 模型导入
自动导入所有 SQLModel 模型以支持 autogenerate:
from app.models.user import User, UserSession
from app.models.folder import Folder
from app.models.project import Project, ProjectMember
from app.models.credit import CreditTransaction, CreditConsumptionLog
# ... 更多模型
3. Jointo 规范集成
禁止外键约束
def render_item(type_, obj, autogen_context):
"""自定义渲染逻辑"""
# 禁止自动生成外键约束(Jointo 规范)
if type_ == "foreign_key":
return False
return False
过滤对象
def include_object(object, name, type_, reflected, compare_to):
"""过滤要包含的数据库对象"""
# 忽略 Alembic 自己的版本表
if type_ == "table" and name == "alembic_version":
return False
# 忽略旧的迁移记录表
if type_ == "table" and name == "schema_migrations":
return False
return True
4. 便捷脚本
scripts/db_migrate.py 提供友好的命令行界面:
- 彩色输出
- 详细帮助信息
- 安全确认(回滚操作)
- 错误处理
使用示例
创建新迁移
# 1. 修改模型
# server/app/models/user.py
class User(SQLModel, table=True):
user_id: UUID = Field(default_factory=uuid7, primary_key=True)
username: str = Field(max_length=255, unique=True)
phone: Optional[str] = Field(default=None, max_length=20) # 新增
# 2. 生成迁移
python scripts/db_migrate.py create "添加用户手机号字段" --autogenerate
# 3. 检查生成的文件
# server/alembic/versions/20260127_1430_abc123_添加用户手机号字段.py
# 4. 执行迁移
python scripts/db_migrate.py upgrade
回滚迁移
# 回滚一个版本
python scripts/db_migrate.py downgrade
# 确认提示
# 警告:此操作可能导致数据丢失
# 确认继续?(yes/no): yes
查看状态
# 当前版本
$ python scripts/db_migrate.py current
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
Current revision for postgresql://...: abc123 (head)
# 迁移历史
$ python scripts/db_migrate.py history
abc123 -> xyz789 (head), 添加用户手机号字段
xyz789 -> def456, 创建项目表
def456 -> <base>, 初始迁移
迁移路径
从旧系统迁移
如果数据库已使用旧迁移系统:
# 1. 创建基线迁移
python scripts/db_migrate.py create "baseline" --autogenerate
# 2. 标记为已应用(不执行)
python scripts/db_migrate.py stamp head
# 3. 验证
python scripts/db_migrate.py current
新项目
直接使用 Alembic:
# 1. 创建初始迁移
python scripts/db_migrate.py create "initial" --autogenerate
# 2. 执行迁移
python scripts/db_migrate.py upgrade
最佳实践
1. 开发流程
- 修改模型
- 生成迁移:
create "描述" --autogenerate - 检查生成的代码
- 测试 upgrade 和 downgrade
- 提交代码
2. 命名规范
- 使用动词开头:
添加、修改、删除 - 包含表名和字段名
- 清晰明确
3. 代码审查
- 检查是否符合 Jointo 规范
- 验证 downgrade 逻辑
- 确认数据迁移安全
4. 生产部署
# 1. 备份数据库
pg_dump -U postgres jointo > backup.sql
# 2. 执行迁移
python scripts/db_migrate.py upgrade
# 3. 验证
python scripts/db_migrate.py current
优势对比
| 功能 | 旧系统 | Alembic |
|---|---|---|
| 自动生成 | ❌ | ✅ |
| 版本管理 | ⚠️ 手动 | ✅ 自动 |
| 回滚支持 | ⚠️ 不完整 | ✅ 完整 |
| 模型同步检测 | ❌ | ✅ |
| 分支合并 | ❌ | ✅ |
| 依赖管理 | ❌ | ✅ |
| 状态查询 | ⚠️ 基础 | ✅ 详细 |
| 社区支持 | ❌ | ✅ |
注意事项
1. 外键约束
Alembic 默认会生成外键约束,但 Jointo 规范禁止使用。已在 env.py 中配置自动过滤:
def render_item(type_, obj, autogen_context):
if type_ == "foreign_key":
return False # 禁止生成外键
return False
2. 枚举类型
使用 SMALLINT 而非 PostgreSQL ENUM:
# ✅ 正确
sa.Column('status', sa.SmallInteger(), nullable=False)
# ❌ 错误
sa.Column('status', sa.Enum('active', 'archived', name='status'), nullable=False)
3. UUID v7
确保使用 UUID v7 函数:
sa.Column('id', sa.UUID(), server_default=sa.text('gen_uuid_v7()'), nullable=False)
4. 时间戳
使用 TIMESTAMPTZ 和触发器:
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False)
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False)
后续计划
短期
- 创建基线迁移标记当前数据库状态
- 归档旧迁移文件到
.archive/ - 更新部署文档
中期
- 添加迁移测试自动化
- 集成到 CI/CD 流程
- 添加迁移性能监控
长期
- 探索零停机迁移策略
- 实现迁移回滚自动化
- 建立迁移最佳实践库