bigywater.tech
Django · 55

Migration 执行失败后的恢复方式

迁移跑到一半失败了,库里是什么状态?下一次 migrate 会怎样?这篇故意写一个「先成功、后失败」的迁移,分别在 atomic 与非 atomic 下跑,再演示两种恢复办法。

atomic
默认(PostgreSQL 事务性 DDL):失败全部回滚,库里没有残留、没有记录
半应用
atomic = False:前面的操作已提交,迁移没有记录,再 migrate 会因「列已存在」失败
原则
恢复 = 让数据库实际状态与 django_migrations 记录一致

atomic = True(默认)

实测 · 本机执行输出
  migrate 结果最后一行: django.db.utils.ProgrammingError: syntax error at or near "THIS"
  c1 列存在: 0  django_migrations 有记录: 0

PostgreSQL、SQLite 支持事务性 DDL,所以整个迁移是一个事务。MySQL 不支持(DDL 会隐式提交),那里失败就是半应用状态(本文没有测)。

atomic = False

实测 · 本机执行输出
  migrate 结果最后一行: django.db.utils.ProgrammingError: syntax error at or near "THIS"
  c1 列存在: 1  django_migrations 有记录: 0  ← 库处于「半应用」状态
  再跑一次 migrate(第一条操作重复执行):
   django.db.utils.ProgrammingError: column "c1" of relation "lab_p" already exists

需要它的场景:CREATE INDEX CONCURRENTLY、大数据回填分批提交。代价就是失败后自己收拾。写非 atomic 迁移时,建议每个迁移只放一个操作,失败后现场容易判断。

恢复办法一:修到一致

实测 · 本机执行输出
  手工撤销第一条(DROP COLUMN c1)后 c1 存在: 0
  修好迁移文件之后 migrate: ['Applying lab.0002_fail... OK']  记录: 1

手工撤销已执行的部分,让迁移可以「从头再来」;或者把剩下的部分手工做完再 --fake。

恢复办法二:--fake

实测 · 本机执行输出
  migrate lab 0001 --fake: ['Unapplying lab.0002_fail... FAKED']  c1 列仍存在: 1  记录: 0

--fake 只改记录。用之前要确认库里的实际状态确实与你要标记的迁移一致。

分步演示:迁移失败之后

迁移失败与恢复
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。PostgreSQL 16.15(本机临时实例,端口 9431)
    • 没有验证:MySQL(DDL 隐式提交)上的半应用、SQLite 的事务性 DDL、大表迁移中途被终止(kill)时的锁与残留、迁移里 RunPython 失败时的行为
    京ICP备2026067502号-1京公网安备11010502063487号