关于Django迁移操作的一些说明

2021年6月25日 作者 Just

django model中的表与数据库中的table在建立联系时, 共有三个角色我们需要了解:

  • model:只会展示现在django层面上的model是什么样子,有哪些字段/约束;
  • Migrations文件:记录了model的变化过程,对于model, 他是观察者,每次model变化后执行迁移,他都会记录下来在那一刻model的变化内容;
  • 数据库中的 django_migrations 表:记录在 Migrations 的所有步骤中,当前的 数据库Table 已经执行了哪几步,这样就可以只执行没有执行过的步骤,来变成最新的状态;

这里特别说明一点, 如果生成并执行了迁移文件001,002, 随后删除并重新生成了新的001-01并且进行了执行, 那么 django_migrations 表里面会有三条记录,分别是001,002,001-01, 换言之, 删掉已经执行的迁移文件并不会删除其在django_migrations 表已经生成的记录.

一次 migration 之旅

Django ORM 基本屏蔽了用户需要做的 SQL 操作,包括 DDL 操作。

从一个用户的角度看,我们做一次 migration,只需要3步:

  • 修改 models.py,比如添加字段;
  • 执行 python manage.py makemigrations ;
  • 执行 python manage.py migrate ;

这时候,数据库中的 Table 和我们 Django 中的 Model 就对上了,我们写业务逻辑就可以了。

但实际上,Django 在背后做了什么呢?如果不了解这些,遇到一些问题就束手无策了。

在整个流程中,Django 负责的是自动识别出 Model 进行了哪些变化,将这些变化应用到 Table 中,保证最终 Model 和 Table 还是对应的。本质上,就是将 py 文件的变化,转成数据库的 DDL 变化。

所以首先,Django 要知道你的 Model 做了哪些变化。这是第2步:生成 migrations。

Migrations 生成的方式,颇有点现在“声明式”的意思。

它的过程是这样的(这里我们将修改后的 Model 记作 Model-2,修改前的 Model 记作 Model-1):从 migrations/ 文件夹下的 0001_xxx.py 开始 apply,到 0002_xxx.py,一直到最后一个文件,这些文件都记录了 Model 的变化,都 apply 一遍之后,就得到了修改前的的 Model-1 的状态,然后和修改过的 Model-2 来比较,得到了 diff,然后看如何消除这些 diff——即产生最新的 migrations 文件,将 Model-1 的状态转移到 Model-2.这样新的 migrations 就生成了,如果你再跑一遍的话,就发现什么 migrations 也不会出现,因为 migrations 文件记录的变化已经和最新的 Model 一样了(声明式天生的幂等性!)。

这些 migrations 记录了 Model 从 0001_init 开始的所有变化,有了 migrations 文件,你就可以从一个空的数据库,变成现在的结构了。

但是如果数据库不是空的,而是已经有一些结构呢?通常是这种情况:生产环境的数据库,表结构是 v3(执行过migrations 0001,0002,0003),你在开发的时候生成了 migrations 0004,0005。那么怎么知道这个数据库只要执行 0004,0005 就可以了呢?更复杂一点的话,假如你有一个灰度服务器,一个生产服务器,灰度服务器执行过了 0001-0004,生产服务器落后一个版本,执行了0001-0003,怎么知道这些数据库应该执行哪些 migrations 呢?

这里根本的问题是:migrate 的执行(DDL的执行)不是幂等的。所以我们就需要一个地方,记录一下哪些 migrations 已经被执行过了。Django 会自动在你的数据库中建立一张 django_migrations 表,用来记录执行过的 migrations。这样你在执行 python manage.py migrate 的时候,django 会去对比 django_migrations 表,只执行没有执行过的 migrations,无论你有多少表结构版本不同的数据库都没有关系。

下面是一些常见问题的说明

1.migrate执行到一半失败了怎么办?

这种情况是很容易发生的,因为 make migrations 的时候,Django 只知道消除 diff,并不知道表结构。这就很有可能导致生成的 migrations 实际上是违反数据库约束的,而不能执行成功。

最糟糕的情况是,一个 migrations 中有很多步,其中部分执行成功了。就造成数据库的结构处于一个“未知”的状态。

不过不要慌,只要从 log 中看下哪些 migrations 步骤已经执行了,去手动 revert,然后从 django_migrations 删除这些记录即可。参考这篇文章

2.我什么 models 都没改,但是每次 make migrations 都会生成新的?

不要慌,我也遇到过。这时候你去看下生成的 migrations 做了啥操作,再想想 Django 为什么会这样做即可。

举个例子吧,我之前的 models 里面一个 default 值用了字典,我们知道字典 key 是没有顺序的(Python 3.7)之前,就导致每次 make migrations 的时候,顺序都有几率不一样,Django 就认为状态不一致,需要新的 migration 文件来消除 diff。

3.migrations 文件冲突了,我和同事开发的时候,我生成了一个 0003_xxx.py 我同事也生成了这个 0003.

不要慌,这是正常现象。按照 Django 的提示,执行生成一个新的 migrations 来 merge 前面的两个就好啦。python manage.py makemigrations –merge 。

4.我的migrations太多了,每次 makemigrations 都要等很久,或者我的 migrations 文件名已经快到 999_xxx.py 了!

解决方法也很简单,因为我们只需要一个最终的状态,所以我们可以将之前的 migrations 都删掉。

步骤如下:

备份整个项目和数据库(非常重要);
删除除了 init.py 之外的所有 migrations 文件夹下的文件:ls /migrations/.py | grep -v init | xargs rm;
重新生成 migrations 文件,不出意外的话,所有的 app 都只有一个 migration 文件,就是 init;
删除数据库 django_migrations 表中所有的记录:delete from django_migrations;
因为我们的表结构已经是最新的了,所以新生成的 init migrations 并不需要执行。所以我们插入记录到 django_migrations 中,骗 djagno 我们已经执行过了。用这个命令:python manage.py migrate –fake;
大功告成,不出意外,makemigrations 又快了起来。

一些奇技淫巧

1.如何查看一个 migration 文件对应的 SQL 是啥?

python manage.py sqlmigrate messenger 0084

(python manage.py sqlmigrate app名字 迁移文件编号)

image

查看这个数据库执行过哪些migrations了?

([X]表示已经执行, []表示没有执行)
image

最后 n 次执行的 migrations 有问题,数据库炸了,要不要删库跑路?

也不是不行(划掉)

注意! Unapplying只会恢复表结构,如果你有删除某个字段的操作,那么数据通过这种方法是无法恢复的! (网上说法, 我没有实际实验)

➜ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, users
Running migrations:
  Applying users.0003_auto_20190803_1550... OK
  Applying users.0004_auto_20190803_1550... OK
➜ python manage.py migrate users 0002
Operations to perform:
  Target specific migration: 0002_auto_20190803_1549, from users
Running migrations:
  Rendering model states... DONE
  Unapplying users.0004_auto_20190803_1550... OK  
  Unapplying users.0003_auto_20190803_1550... OK
➜ python manage.py showmigrations users
users
 [X] 0001_initial
 [X] 0002_auto_20190803_1549
 [ ] 0003_auto_20190803_1550
 [ ] 0004_auto_20190803_1550

其他的一些参考资料:
Django的数据迁移(Data migration)

记一次Django数据迁移Bug

本文来源(我略加增删):
Django migration 原理