第一次用Django的人,十有八九会被数据库迁移搞懵。明明代码里写好了模型,运行却报错;明明改了字段类型,数据库就是不更新。我当年第一次跑的时候,看着终端里冒出的那一堆“Alter field”提示,心里想的全是:这玩意儿到底在干嘛?后来踩了无数坑才明白,迁移系统本质上就是Django帮你管理数据库结构变更的一本账本,每次改动模型,它都记一笔,然后按顺序执行。理解了这一点,后面的一切就顺理成章了。

先说说最基础的流程。你写完模型,先运行,Django会对比当前模型定义和数据库里已有的结构,生成一个迁移文件,放在应用目录的文件夹里。这个文件就是“账本上的一笔记录”,它描述了你要做什么改动——加个字段、删个表、改个长度,全都在里面。然后你运行,Django才真正把这些改动应用到数据库。很多人上来就只跑,完全跳过,结果数据库一点动静没有,还以为是代码写错了。记住,这两个命令是一对,缺一不可。
但真正的坑不在基础流程,而在你改动模型之后。我举个最简单的例子:你给一个已有数据的表添加一个非空字段,没给默认值。这时候跑,Django会停下来问你:你给这个字段设什么默认值?如果你直接按回车,它会给你一个的选项——但如果你没看清楚就确认了,后面所有查询这个字段的代码都会因为拿到而炸掉。更隐蔽的情况是,你在生产环境改了字段类型,比如把改成,如果表里有非数字的字符串,迁移执行到一半就会报错。这时候数据库可能处于一个“半迁移”状态,你既不能继续,也回不去。
这种时候,就得学会看迁移文件本身。Django生成的迁移文件就是个Python文件,里面是列表,每个元素对应一个操作。你可以手动编辑这些文件,比如在添加字段的操作里加上,或者在改类型之前先写一个操作,把旧数据清洗一遍。我有个习惯:每次改动模型,都会打开生成的迁移文件看一眼,确认Django理解的和我想的一致。如果不一致,宁可删掉这个迁移文件重新生成,也不留着隐患。反正开发环境里表数据不重要,删了重建都行。
再说说数据迁移,这个很多人压根不知道存在。你以为迁移只能改结构?其实还能改数据。比如你有个字段,原来是布尔值,现在要改成状态码,1表示激活,0表示禁用。单纯的字段改名和类型转换解决不了问题,你需要把旧数据里的变成1,变成0。这时候就得写一个数据迁移:先生成一个空迁移文件,然后在里面写函数,读取旧数据,转换后写回去。这个操作在生产环境特别有用,因为手动跑SQL容易出错,而且没记录在案。用数据迁移,所有的变更都有迹可循,回滚也方便。
说到回滚,这是另一个被低估的功能。Django的迁移是支持反向操作的,就能回到第10个迁移的状态。但前提是你得保证每个迁移文件都有对应的反向操作。Django自动生成的那些增删字段的操作,基本都有反向,不用操心。但你自己写的,如果只写了正向函数没写反向,回滚的时候就会报错。我见过一个团队,为了赶进度,手动写了个数据清洗操作,没写反向,结果上线两周后要回滚,整个迁移链卡死,只能手动改数据库,那叫一个酸爽。所以自己写数据迁移的时候,哪怕反向函数只是打印一行日志说“这个操作不可逆”,也一定得写上,至少让Django知道该怎么处理。
还有一个高频问题:多个开发者同时改模型,生成了一堆冲突的迁移文件。比如你改了模型加了个字段,同事也改了模型加了另一个字段,你们各自的迁移文件都基于同一个父迁移。合并代码后,跑会提示你检测到冲突,需要合并。这时候别慌,就能搞定,Django会自动生成一个合并迁移,把两条线合并成一条。但合并之前最好先看看两个迁移文件有没有改到同一个字段,如果改的是同一个字段,那合并也没用,得手动解决冲突,保留一个删掉另一个。
聊聊生产环境的迁移策略。我见过太多人直接在服务器上跑,这其实是个坏习惯。生产环境应该只跑,迁移文件必须在开发环境生成好,提交到代码仓库,然后部署的时候在服务器上执行。原因很简单:需要对比模型和数据库状态,生产环境的数据库可能有历史数据,生成的迁移文件可能和开发环境不一样,容易出问题。另外,迁移执行前一定要备份数据库。哪怕你的迁移文件写得再完美,也架不住数据库环境差异,比如MySQL的版本不同,字符集不同,都可能让迁移失败。备份了,出问题还能恢复;没备份,就只能对着报错信息干瞪眼。
从我这几年的经验看,Django迁移系统只要摸透了它的脾气,其实挺省心的。核心就三条:第一,每次改动模型都要走加的流程,别偷懒;第二,生成的迁移文件一定要看,别盲目执行;第三,生产环境永远只跑,迁移文件全部从仓库里来。做到这三点,数据库迁移基本不会出大乱子。当然,真要遇到极端情况,比如迁移文件损坏、数据库状态不一致,那也没什么捷径,老老实实把数据库备份恢复,然后删掉所有迁移文件重新生成。反正数据还在,结构乱了重建就是了。这套流程我用了好几年,踩过的坑都变成了经验。


