alembic 默认同步执行,与fastapi+sqlalchemy异步模式冲突,需为迁移单独配置同步引擎、统一用同步base定义模型,并确保env.py中正确导入模型和赋值target_metadata。

为什么直接用 alembic init 会报错 “Event loop is closed”
因为 Alembic 默认同步执行,而 FastAPI + SQLAlchemy 异步模式(AsyncEngine)下,create_async_engine 创建的引擎不能被同步 ORM 操作直接调用。你在 env.py 里若直接调 engine.connect() 或 run_migrations_online(),就会触发事件循环已关闭的错误——本质是 Alembic 启动时没接管异步上下文。
实操建议:
- 不要在
env.py中直接 await 异步操作;Alembic 运行时没有运行中的 event loop - 改用
sync_engine做迁移:为 Alembic 单独配置一个同步数据库 URL(哪怕和异步 URL 完全一样),用create_engine而非create_async_engine - 确保
target_metadata指向你实际定义模型的MetaData对象(通常是Base.metadata),且该对象由同步版declarative_base()构建(不是async_sessionmaker所依赖的异步 Base)
如何让 alembic revision 正确识别模型变更
Alembic 不会自动扫描你的 .py 文件,它只认 env.py 中显式导入并赋值给 target_metadata 的 MetaData。如果你用的是异步声明基类(比如 DeclarativeBase = declarative_base()),但模型文件里又用了 class User(Base): ...(其中 Base 是异步基类),那 Base.metadata 实际上是空的——因为异步基类默认不挂载表元数据到全局 MetaData。
实操建议:
- 统一使用同步
Base定义模型:即使业务代码用异步 session,模型类本身仍应继承自标准declarative_base() - 在
env.py开头显式导入模型模块,例如from app.models import Base,再设target_metadata = Base.metadata - 运行
alembic revision --autogenerate -m "init"前,确认所有模型已 import 到env.py可见作用域
alembic upgrade head 成功但表没创建?检查这三处
常见现象:命令返回 OK,但连上数据库发现表完全不存在,或只有 alembic_version 表。这不是 Alembic 失败,而是它根本没拿到你的模型元数据。
实操建议:
- 检查
env.py中target_metadata是否为None或空MetaData()对象(打印target_metadata.tables可验证) - 确认
SQLALCHEMY_DATABASE_URL在env.py中指向的是真实可写的数据库(不是内存 SQLite 或拼写错误的 host) - 避免在模型文件中条件导入(如
if TYPE_CHECKING:包裹模型定义),这类代码不会被 Alembic 执行时加载
异步应用启动时如何安全触发 alembic upgrade
别在 main.py 里直接调 os.system("alembic upgrade head") ——这不可靠、难调试、无法捕获错误。更糟的是,如果多个 worker 同时启动,可能并发执行 migration。
实操建议:
- 把迁移逻辑封装成独立函数,用
alembic.config.Config和alembic.command.upgradeAPI 调用(非 shell) - 只在主进程(如 Uvicorn 的
on_startup)中执行一次,加文件锁或 DB 锁判断是否已执行 - 生产环境更推荐:将
alembic upgrade head作为部署脚本的一部分,在服务启动前手动/CI 执行,而非耦合进应用生命周期
复杂点在于:异步框架和同步迁移工具的边界必须划清。很多人试图“彻底异步化 Alembic”,结果掉进 asyncio 线程池、loop 传递、contextvars 泄漏的坑里。稳住,用同步引擎管迁移,用异步引擎管业务,才是最小心智负担的路径。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











