bigywater.tech
Flask · 98

数据迁移回滚

迁移失败了会怎样?回滚能把数据找回来吗?为什么不同数据库上失败后的状态不一样?这篇用真实的 PostgreSQL 与 SQLite 把几种失败与回滚的结局放在一起对照。

NotNullViolation / 半应用
同一个迁移失败时,PostgreSQL 整体回滚,SQLite 留下 a_col
数据回不来
downgrade 删掉的列再 upgrade 回来之后值是 None
-- -1
flask db downgrade 回退一步的写法

回滚丢数据

实测 · 本机执行输出
  upgrade 后: [('ann', 42)]
  downgrade 后的列: ['id', 'name']
  再 upgrade 后: [('ann', None)] (score 的值丢了)

上一篇:Flask-Migrate Migration 文件;下一篇:SQLite 与 PostgreSQL 行为差异。

迁移到一半失败

实测 · 本机执行输出
  [pg] upgrade 退出码 1:sqlalchemy.exc.IntegrityError: (psycopg.errors.NotNullViolation) column "b_col" of relation "user" contains null values
  [pg] 失败之后的列: ['id', 'name'] | alembic_version: r001
  [pg] 修好问题前直接再 upgrade 一次 → 退出码 1:sqlalchemy.exc.IntegrityError: (psycopg.errors.NotNullViolation) column "b_col" of relation "user" contains null values
  [sqlite] upgrade 退出码 1:sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) Cannot add a NOT NULL column with default value NULL
  [sqlite] 失败之后的列: ['id', 'name', 'a_col'] | alembic_version: ['r001']
  [sqlite] 修好问题前直接再 upgrade 一次 → 退出码 1:sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) duplicate column name: a_col

不可逆迁移

实测 · 本机执行输出
  downgrade → 退出码 1: ERROR [flask_migrate] Error: 这个迁移不可逆:原来的大小写已经丢了
  版本记录仍在 r002: ['r002']

downgrade 的目标

实测 · 本机执行输出
  upgrade 后版本: ['r003'] 列: ['id', 'name', 'c2', 'c3']
  downgrade -- -1 后: ['r002'] ['id', 'name', 'c2']
  downgrade r001 后: ['r001'] ['id', 'name']
  downgrade base 后: [] | 表: ['alembic_version']

分步演示

数据迁移回滚
本步的对象

    本文用到的工具与边界

    • 本机 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)、自己起的 PostgreSQL(非默认端口)、SQLite
    • 没有验证:MySQL(DDL 会隐式提交)上的半应用状态、transaction_per_migration 配置、PostgreSQL 上 CREATE INDEX CONCURRENTLY 这类不能在事务里执行的语句、多步迁移分成多个文件的做法、大表上回滚的耗时与锁
    京ICP备2026067502号-1京公网安备11010502063487号