Django 自从 1.7 版本之后,提供了方便的 migrate 操作,可以通过命令行直接监视模型的变化,自动生成数据库的 alter 脚本,只需要通过:
python manage.py makemigrations
然后就会在 /app/migrations
目录下面将数据库的变动添加一个处理脚本进去。
这一般情况下是很方便的,但是总会有那么些意外情况,下面两种是经常会遇到的问题:
1. 开发环境与生产环境的同步时
在开发环境下面做了一些模型的修改,在开发环境下做了两次 makemigrations,然后把变动代码发到生产环境中,然后又在生产环境 makemigrations;
这种情况下,开发环境下面的 migrations 文件夹多了两个文件,而生产环境里面只多了一个文件,这个时候 migration 已经不同步了,然后在生产环境更新数据库:
python manage.py migrate
的时候,就有可能报错!而且常规的方法(不变动数据库)是没有办法修复的。
2. 对于一些存在数据库冲突的操作
例如,在模型中将一个空字段改为非空字段,生产环境里面没有数据,migrate 的时候很顺畅,但是开发环境里面有数据,那就卡死了。
问题分析
1. 怎样的状态 migration 才是正常的?
这需要有几点:
- 已经通过
makemigrations
命令将最近的模型结构产生了/migrations
文件; - 当前的 migration 已经同步到数据库的结构中,也就是执行过 migrate 命令;
- 逻辑数据保全;
2. 出现问题之后的状态
一般而言,出现问题之后,开发环境的数据价值不高,前面两点总是可以保证的。
退一万步说,将数据库清空,将 /migrations
的文件清除,重新跑一下 makemigrations
和 migrate
命令,前面的两点就可以确保了,即使生产环境不同步;
对于生产环境而言,前面两点可能都打乱了,但是第三点仍然没问题(数据还在)。
解决方案
-
将开发环境的前两点确保之后,将
/migration
文件同步到生产环境; -
在生产环境中,仅导出数据,不导出结构:
mysqldump -t -c -u root -p db_name > db_data_only.sql
这里 -t
为不导出表结构,-c
为完整的 insert 语句(包含字段名)
- 从开发环境中,仅导出数据结构:
mysqldump -d -u root -p db_name > db_struct_only.sql
注意这一步导出的数据结构是满足第二点的。
-
清空生产环境的数据库,删掉重新创建一个空数据库。
-
生产环境导入数据结构:
mysql -u root -p db_name < db_struct_only.sql
- 生产环境导入原来的数据:
mysql -u root -p db_name < db_data_only.sql
这样的话再考虑一下,开发环境上述三点都已经修复了。
例外中的例外
上述处理步骤中,第四和第五步之间可能还会出问题:
例如上面说的将可空字段变成非空,或者有外键约束失败的时候。
这个时候,就需要做一些细微的处理,这个时候应该数据是会导入失败的。
这时候在最终的数据结构里面,将产生冲突的约束先干掉,例如,在数据库中将非空改为可空,把外键约束删掉。
然后将数据导入。
然后将那个字段的空值补全,将外键丢失的补全。
然后再 -t -c
导出数据。
然后再删除数据库、导入结构、导入数据这样再做一遍。
中间的各种折腾,就请自行脑补了,卒。