bigywater.tech
Django · 54

数据迁移中的正向与反向操作

「迁移是可逆的」到底可逆到什么程度?RunPython 不写反向会怎样?删除字段再回退,数据回来了吗?这篇把每种情形都真的回退一遍。

IrreversibleError
RunPython 没有 reverse_code,回退时直接抛这个异常
99 → 7
删列再回退,列回来了,但值是 default(7),不是删除前的 99
空表
DeleteModel 回退重建的是空表,数据全部丢失

RunPython 的反向

实测 · 本机执行输出
  migrate lab 0001 → django.db.migrations.exceptions.IrreversibleError: Operation <RunPython <function Migration.<lambda> at 0x10b023b60>> in
  加 RunPython.noop 作为 reverse_code:
  → ['Unapplying lab.0002_data... OK']

没有反向就不能回退,这是好事:强迫你想清楚。确实不需要反向时,写 RunPython.noop 明确表态。

反向不检查互逆

实测 · 本机执行输出
  正向后: [('KEEP',)]
  反向(noop)后: [('KEEP',)]  ← 数据没有还原

Django 只负责在回退时调用你的反向函数,不验证它撤销了正向做的事。

自动迁移的反向

实测 · 本机执行输出
  加列后把 extra 改成 99: [('KEEP', 99)]
  删列后: [(1, 'KEEP')]
  回退「删列」之后(列回来了): [('KEEP', 7)]  ← 数据是 default(7),不是删除前的 99

结构可以回退,数据不能。所以在生产环境「回退迁移」不是备份的替代品。

DeleteModel

实测 · 本机执行输出
  删除前行数: [(1,)]
  删除模型后表还在吗(0=不在): [(0,)]
  回退 DeleteModel 后:表在吗(1=在) [(1,)]  行数 [(0,)]

同上。要保留数据,回退之前必须先备份。

分步演示:回退的真相

迁移的反向
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;每步真实执行 migrate 并用 SQL 查看数据
    • 没有验证:PostgreSQL 上同样的回退(数据行为应相同,但未测)、RunSQL 的 reverse_sql、AlterField 回退时数据类型转换失败的情形、elidable 标记
    京ICP备2026067502号-1京公网安备11010502063487号