Alembic反向生成升级/降级迁移问题求助(FastAPI+PostgreSQL)
问题
使用Alembic + FastAPI + PostgreSQL时,数据库为空的情况下,执行自动生成迁移脚本命令,得到的upgrade函数是删除表,downgrade函数是创建表的反向逻辑。即使手动调换代码,下次执行自动迁移还是会生成删除语句。
示例执行命令及输出
alembic revision --autogenerate -m "Testing 2" INFO [alembic.runtime.migration] Context impl PostgresqlImpl. INFO [alembic.runtime.migration] Will assume transactional DDL. INFO [alembic.autogenerate.compare] Detected removed index 'ix_Site_keyword' on 'Site' INFO [alembic.autogenerate.compare] Detected removed table 'Site' Generating /app/migrations/versions/1e3a0f40182c_testing_2.py ... done
生成的迁移文件内容
"""Testing 2 Revision ID: 1e3a0f40182c Revises: 928ab2a61fa7 Create Date: 2024-04-04 08:32:29.784316 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. revision: str = '1e3a0f40182c' down_revision: Union[str, None] = '928ab2a61fa7' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_index('ix_Site_keyword', table_name='Site') op.drop_table('Site') # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table('Site', sa.Column('id', sa.UUID(), autoincrement=False, nullable=False), sa.Column('title', sa.VARCHAR(), autoincrement=False, nullable=True), sa.Column('keyword', sa.VARCHAR(), autoincrement=False, nullable=True), sa.Column('description', sa.TEXT(), autoincrement=False, nullable=True), sa.Column('is_active', sa.BOOLEAN(), autoincrement=False, nullable=True), sa.Column('created_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True), sa.Column('updated_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True), sa.Column('deleted_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True), sa.PrimaryKeyConstraint('id', name='Site_pkey') ) op.create_index('ix_Site_keyword', 'Site', ['keyword'], unique=False) # ### end Alembic commands ###
附env.py文件内容
from logging.config import fileConfig from sqlalchemy import engine_from_config from sqlalchemy import pool from database.orm import Base from alembic import context config = context.config if config.config_file_name is not None: fileConfig(config.config_file_name) target_metadata = Base.metadata def run_migrations_offline() -> None: url = config.get_main_option("sqlalchemy.url") context.configure( url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"}, ) with context.begin_transaction(): context.run_migrations() def run_migrations_online() -> None: connectable = engine_from_config( config.get_section(config.config_ini_section, {}), prefix="sqlalchemy.", poolclass=pool.NullPool, ) with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata ) with context.begin_transaction(): context.run_migrations() if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online()
原因与解决办法
核心原因
Alembic自动生成迁移的逻辑是对比当前数据库状态和target_metadata(即你的Base.metadata)的差异:
- 你的数据库是空的,但
Base.metadata里有Site表的定义 - Alembic会误判:把数据库的空状态当成“目标状态”,把模型里的表结构当成“历史状态”,所以生成的
upgrade是删除表(从有表改成无表),downgrade是创建表(从无表改成有表) - 另外如果
alembic_version表中存在旧迁移记录,且该记录对应的操作是创建过Site表,Alembic会认为当前数据库应该有这个表,现在缺失了,进一步强化“需要删除”的错误判定。
解决步骤
初始化空数据库的迁移基线
- 若为全新空库,先创建初始迁移并同步到数据库:
此时生成的# 若未初始化Alembic先执行(已初始化可跳过) alembic init migrations # 生成初始结构迁移 alembic revision --autogenerate -m "initial schema" # 同步结构到数据库 alembic upgrade headupgrade会是创建表的逻辑,同时alembic_version表会记录当前正确版本。
- 若为全新空库,先创建初始迁移并同步到数据库:
修复现有错误迁移
- 删除
migrations/versions/下的错误迁移文件 - 清空数据库中的
alembic_version表(若有旧记录) - 重新执行上述初始化步骤,生成正确的初始迁移并执行。
- 删除
规范后续迁移流程
- 修改模型后,先执行
alembic revision --autogenerate生成迁移脚本 - 检查
upgrade和downgrade逻辑是否符合预期(比如新增字段应在upgrade中添加,downgrade中删除) - 执行
alembic upgrade head同步到数据库
- 修改模型后,先执行
额外注意
- 不要手动修改迁移文件后再执行
--autogenerate,Alembic会基于当前数据库和模型的差异重新生成,手动修改会被覆盖 - 确保
env.py中的target_metadata = Base.metadata正确指向你的模型基类,保证Alembic能读取到最新模型结构
内容的提问来源于stack exchange,提问作者sincorchetes
相关产品推荐
相关产品推荐

