说实话,我第一次用Django做迁移的时候,被那些命令搞得一头雾水。makemigrations、migrate、sqlmigrate,还有一堆带参数的变体,看着都差不多,用起来却天差地别。更让人崩溃的是,有时候一条命令输错,数据库就跟你翻脸不认人,数据表建了又删,删了又建,折腾半天也不知道哪里出了问题。后来踩的坑多了,才慢慢摸清楚这些命令的脾性。今天这篇文章,我就把这几年来积累的Django迁移实战经验掏出来,。

先说说最基础的三板斧。makemigrations负责生成迁移文件,migrate负责把迁移应用到数据库,sqlmigrate则让你预览即将执行的SQL语句。这三个命令看着简单,但里面藏着不少门道。比如说makemigrations,很多人不知道它有个--dry-run参数,可以在不实际生成文件的情况下,预览会创建哪些迁移。这个参数在你想确认模型改动是否符合预期时特别好用,省得生成了一堆没用的迁移文件,回头还得手动清理。还有--name参数,可以给迁移文件起个有意义的名字,比如makemigrations --name adduseravatar,这样以后翻看迁移历史的时候,一眼就能看出这个迁移干了什么,不用挨个打开文件去猜。
migrate命令的学问更大。--plan参数会显示将要执行的迁移计划,包括哪些迁移会应用,哪些会被跳过,以及它们之间的依赖关系。这个功能在多人协作的项目里尤其重要,因为经常会出现别人的迁移和你本地的迁移冲突的情况。用--plan提前看看,就能发现问题在哪,避免直接执行后报错。还有--fake参数,这个是个双刃剑。它只标记迁移为已应用,但不会真正执行SQL。什么时候用呢?比如你手动改了数据库结构,跟迁移文件对不上了,或者从老项目迁移过来,表已经存在了,这时候用--fake就能让Django认为迁移已经执行过,避免重复建表报错。但用--fake要格外小心,一旦标记错了,后续的迁移就会出现各种诡异的问题。
再来说说迁移文件本身。很多人觉得迁移文件是自动生成的,不用管它,这个想法害人不浅。迁移文件其实就是普通的Python代码,你完全可以手动修改。比如你发现某个迁移里的字段类型写错了,与其删掉重来,不如直接改迁移文件。但有个前提,改迁移文件之前,一定要确保这个迁移还没有被其他人执行过,否则他们的数据库跟你的就对不上了。还有个好习惯是,每次生成迁移后,打开文件看一眼,确认里面的操作确实是你想要的。有时候模型里一个小小的改动,比如给字段加个default值,生成的迁移可能包含一堆你没预料到的操作。
在团队协作中,迁移冲突是家常便饭。两个人同时改了同一个模型,生成了两个迁移文件,合并代码的时候就冲突了。这时候别慌,有个简单的处理办法。先把对方的代码拉下来,然后删除自己刚生成的迁移文件,重新运行makemigrations,让Django基于最新的代码重新生成一个合并后的迁移。这种方法虽然会丢失一些迁移历史,但胜在简单可靠。如果两个迁移都已经提交到远程仓库了,那就得用migrate --merge命令,让Django自动处理冲突。但--merge只是把迁移合并成一个,内部操作可能还是有问题的,所以合并完之后,一定要跑一遍测试,确保数据一致性。
还有一个很多人不知道的命令是showmigrations。它会列出项目里所有的迁移文件,以及它们的应用状态,已应用的会标个[X],未应用的标个[ ]。这个命令在排查问题的时候特别有用。比如你发现某个表的数据不对,怀疑是某个迁移没执行,运行showmigrations就能一目了然。配合--plan参数,还能看到每个迁移依赖哪些其他迁移。在复杂的项目里,迁移文件可能有几百个,这时候showmigrations就是你最好的导航工具。
迁移数据的操作也值得单独说说。有时候你不只是改表结构,还要把现有数据转换一下。比如把某个字段从CharField改成IntegerField,直接迁移会失败,因为数据格式不兼容。这时候就需要写数据迁移。用makemigrations --empty生成一个空的迁移文件,然后在里面写RunPython代码,手动处理数据转换。这个过程虽然麻烦,但比手动改数据库安全多了。因为数据迁移也是迁移,可以被记录、回滚、在多个环境里重复执行。
回滚操作是另一个经常被忽视的功能。migrate命令可以指定迁移到某个特定的迁移文件,比如migrate myapp 0042previousmigration,这样就能回滚到那个版本。但回滚不是万能的,如果迁移里有RunPython操作,而且你没有写反向操作,回滚就会报错。所以在写数据迁移的时候,一定要记得写反向代码,哪怕只是写个pass占位符,也比没有强。另外,回滚操作会丢失数据,执行之前一定要做好备份。很多生产事故都是因为回滚操作误操作导致的,这个教训我深有体会。
说说那些容易被忽略的小技巧。迁移文件也是代码,应该被版本控制,这个不用多说。但你知道吗,迁移文件在测试环境里执行的时候,会影响测试速度。如果你的项目迁移文件特别多,每次跑测试都要重新执行几百个迁移,那个酸爽,谁跑谁知道。这时候可以用--keepdb参数,让测试复用现有的测试数据库,跳过迁移步骤。还有,不要在生产环境里用migrate --noinput之前不检查,这个参数虽然能跳过交互式确认,但也意味着你不会看到任何警告信息,万一有问题,直接就是数据库事故。
说了这么多,其实Django的迁移系统本身设计得挺优雅的,只是用起来需要一些经验。最重要的原则就是:动手之前先想清楚,执行之前先看计划。那些看起来繁琐的命令参数,其实都是Django给你提供的安全网。用好了,数据库迁移就是件轻松活。用不好,那就是给自己挖坑。


