bigywater.tech
Flask · 74

Flask-Migrate Migration

Flask-Migrate 的 init、migrate、upgrade 分别做了什么?数据库里哪张表记着当前版本?自动生成的迁移靠得住吗?两个人同时改模型会怎样?这篇用真实的命令把每一步的文件与数据库状态打印出来。

3 步
init / migrate / upgrade:骨架、脚本、执行,只有最后一步改数据库
alembic_version
数据库里记录当前 revision 的表
删 + 加
重命名列被自动识别成的操作,数据会丢

flask db init

实测 · 本机执行输出
  退出码 0
    Creating directory <实验目录>/fltmp/f74/migrations ...  done
    Creating directory <实验目录>/fltmp/f74/migrations/versions ...  done
    Generating <实验目录>/fltmp/f74/migrations/script.py.mako ...  done
    Generating <实验目录>/fltmp/f74/migrations/env.py ...  done
    Generating <实验目录>/fltmp/f74/migrations/README ...  done
    Generating <实验目录>/fltmp/f74/migrations/alembic.ini ...  done
    …(共 7 行)
  生成的文件: ['migrations/README', 'migrations/alembic.ini', 'migrations/env.py', 'migrations/script.py.mako']
  此时数据库里的表: None (还没有迁移,数据库文件都没创建)

上一篇:Flask-WTF 表单验证;下一篇:Flask-Caching。

flask db migrate

实测 · 本机执行输出
  退出码 0
    Generating <实验目录>/fltmp/f74/migrations/versions/8cec42da86e2_create_user_table.py ...  done
    INFO  [alembic.autogenerate.compare.tables] Detected added table 'user'
  迁移脚本里的 upgrade()/downgrade()(节选):
    def upgrade():
        # ### commands auto generated by Alembic - please adjust! ###
        op.create_table('user',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('name', sa.String(length=50), nullable=False),
        sa.PrimaryKeyConstraint('id')
        )
        # ### end Alembic commands ###
    def downgrade():
        # ### commands auto generated by Alembic - please adjust! ###
        op.drop_table('user')
        # ### end Alembic commands ###
  revision / down_revision: 8cec42da86e2 / None
  执行 migrate 之后数据库里的表: ['alembic_version'] (migrate 只生成脚本)

flask db upgrade

实测 · 本机执行输出
  退出码 0
    INFO  [alembic.runtime.migration] Running upgrade  -> 8cec42da86e2, create user table
  数据库里的表: ['alembic_version', 'user']
  user 表的列: [('id', 'INTEGER', 1), ('name', 'VARCHAR(50)', 1)]
  alembic_version 里的值: [('8cec42da86e2',)]
  flask db current: ['8cec42da86e2 (head)']

模型变化后的迁移

实测 · 本机执行输出
  migrate: ["INFO  [alembic.autogenerate.compare.tables] Detected added table 'post'", "INFO  [alembic.autogenerate.compare.tables] Detected added column 'user.email'"]
  第二个迁移脚本 upgrade() 里的操作: ['create_table', 'batch_alter_table', 'add_column']
  downgrade() 里的操作: ['batch_alter_table', 'drop_column', 'drop_table']
  down_revision 指向第一个迁移: True
  upgrade 后表: ['alembic_version', 'post', 'user'] | user 的列: ['id', 'name', 'email']

downgrade 与 history

实测 · 本机执行输出
  flask db history:
    8cec42da86e2 -> 51fc821dbe2a (head), add email and post
    <base> -> 8cec42da86e2, create user table
  downgrade 之后表: ['alembic_version', 'user'] | user 的列: ['id', 'name']

自动生成的局限

实测 · 本机执行输出
  upgrade() 里的操作: [('batch_alter_table', ''), ('', 'add_column'), ('', 'drop_column')]
  相关语句: ["batch_op.add_column(sa.Column('mail', sa.String(length=120), nullable=True))", "batch_op.drop_column('email')"]

多个 head 与 merge

实测 · 本机执行输出
  flask db heads: ['aaaa1', 'aaaa2']
  有两个 head 时 flask db upgrade → ERROR [flask_migrate] Error: Multiple head revisions are present for given argument 'head'; please specify a specific target revision, '<branchname>@h
  flask db merge → ['Generating <项目>/migrations/versions/1c8bb08ac9c6_merge_heads.py ...  done']
  合并后 heads 数: 1

回滚、生产环境的迁移策略见第 97、98 篇。

分步演示

Flask-Migrate 基础
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。Flask-Migrate 4.1.0(Alembic),SQLite 数据库
    • 没有验证:PostgreSQL / MySQL 上的 DDL 事务与锁行为(见第 99 篇)、把重命名列改写成 alter_column 后的效果、online 迁移与大表加列的耗时、flask db stamp 与手工修改 alembic_version
    京ICP备2026067502号-1京公网安备11010502063487号