Django 数据同步失败的处理方法

Django 自从 1.7 版本之后,提供了方便的 migrate 操作,可以通过命令行直接监视模型的变化,自动生成数据库的 alter 脚本,只需要通过:

python manage.py makemigrations

然后就会在 /app/migrations 目录下面将数据库的变动添加一个处理脚本进去。

这一般情况下是很方便的,但是总会有那么些意外情况,下面两种是经常会遇到的问题:

1. 开发环境与生产环境的同步时

在开发环境下面做了一些模型的修改,在开发环境下做了两次 makemigrations,然后把变动代码发到生产环境中,然后又在生产环境 makemigrations;

这种情况下,开发环境下面的 migrations 文件夹多了两个文件,而生产环境里面只多了一个文件,这个时候 migration 已经不同步了,然后在生产环境更新数据库:

python manage.py migrate

的时候,就有可能报错!而且常规的方法(不变动数据库)是没有办法修复的。

2. 对于一些存在数据库冲突的操作

例如,在模型中将一个空字段改为非空字段,生产环境里面没有数据,migrate 的时候很顺畅,但是开发环境里面有数据,那就卡死了。


问题分析

1. 怎样的状态 migration 才是正常的?

这需要有几点:

  1. 已经通过 makemigrations 命令将最近的模型结构产生了 /migrations 文件;
  2. 当前的 migration 已经同步到数据库的结构中,也就是执行过 migrate 命令;
  3. 逻辑数据保全;

2. 出现问题之后的状态

一般而言,出现问题之后,开发环境的数据价值不高,前面两点总是可以保证的。

退一万步说,将数据库清空,将 /migrations 的文件清除,重新跑一下 makemigrationsmigrate 命令,前面的两点就可以确保了,即使生产环境不同步;

对于生产环境而言,前面两点可能都打乱了,但是第三点仍然没问题(数据还在)。

解决方案

  1. 将开发环境的前两点确保之后,将 /migration 文件同步到生产环境;

  2. 在生产环境中,仅导出数据,不导出结构:

mysqldump -t -c -u root -p db_name > db_data_only.sql

这里 -t 为不导出表结构,-c 为完整的 insert 语句(包含字段名)

  1. 从开发环境中,仅导出数据结构:
mysqldump -d -u root -p db_name > db_struct_only.sql

注意这一步导出的数据结构是满足第二点的。
  1. 清空生产环境的数据库,删掉重新创建一个空数据库。

  2. 生产环境导入数据结构:

mysql -u root -p db_name < db_struct_only.sql
  1. 生产环境导入原来的数据:
mysql -u root -p db_name < db_data_only.sql

这样的话再考虑一下,开发环境上述三点都已经修复了。


例外中的例外

上述处理步骤中,第四和第五步之间可能还会出问题:

例如上面说的将可空字段变成非空,或者有外键约束失败的时候。

这个时候,就需要做一些细微的处理,这个时候应该数据是会导入失败的。

这时候在最终的数据结构里面,将产生冲突的约束先干掉,例如,在数据库中将非空改为可空,把外键约束删掉。

然后将数据导入。

然后将那个字段的空值补全,将外键丢失的补全。

然后再 -t -c 导出数据。

然后再删除数据库、导入结构、导入数据这样再做一遍。

中间的各种折腾,就请自行脑补了,卒。