数据库迁移这事儿,说大不大,说小不小。我刚用Flask写项目那会儿,最怕的就是改模型。今天加个字段,明天改个长度,后天删张表,每次都得手动去数据库里折腾,改完还得记得同步给同事。后来用了Flask-Migrate,才发现原来这活儿可以这么省心,今天就把我踩过的坑和总结的经验一次性说清楚。

先说说为什么要用迁移工具。很多人觉得,不就是改个表结构嘛,直接连上数据库执行几条ALTER TABLE不就完了?我当初也这么想,直到有一次在测试环境改了字段名,忘了改代码里的引用,结果线上报错,排查了整整一个下午。数据库结构是跟着代码走的,你改了模型,数据库也得跟着变,但光靠记性,总有漏掉的时候。Flask-Migrate能把你每次对模型的修改记录成一份迁移脚本,就像git管理代码一样管理数据库结构,谁改的、什么时候改的、改了什么,清清楚楚。
安装配置其实很简单,pip install Flask-Migrate,然后在app.py里初始化。有个坑我得提醒你,Flask-Migrate跟Flask-Script是两代人,老教程里教的manager.run()那套已经过时了。现在直接用flask db这个命令就行。你只需要在创建app之后,把Migrate对象初始化进去,然后在启动文件里导入一下command,就完事了。我第一次照着老教程配,配了半天报错,后来才发现是版本兼容问题。
接下来是核心操作,三个命令记住就行:flask db init、flask db migrate、flask db upgrade。init是初始化迁移仓库,会在项目里生成一个migrations文件夹,这个文件夹得提交到git里,团队协作时大家共用一套迁移历史。migrate是自动检测模型改动并生成迁移脚本,upgrade是把脚本应用到数据库。这里有个细节,migrate生成脚本之前,最好先看一眼models.py里有没有语法错误,不然报错信息会很抽象,我见过有人被一个逗号卡了半小时。
生成迁移脚本后,别急着upgrade,先打开migrations/versions目录下的新文件检查一下。自动生成的脚本有时候会犯傻,比如你只是改了个字段长度,它可能给你生成一个删表重建的操作,这要是执行了,数据全没了。我习惯逐行看一遍,确认upgrade函数里的操作符合预期,再顺手写一下downgrade函数,万一改错了还能回滚。这个习惯救过我两次,一次是把字段类型从Integer改成String,自动生成的脚本直接把表删了重建,幸亏我看了眼,手动改成了ALTER COLUMN。
还有一个容易踩的坑是数据库里已有数据的情况。比如你给一个非空字段加默认值,迁移脚本如果只加了NOT NULL约束,老数据就会报错。这时候得在脚本里先给老数据填充一个默认值,再改约束。Flask-Migrate生成脚本时不会自动处理这种逻辑,你得自己写。我通常的做法是,在upgrade函数里先update,再alter,顺序不能反。另外,如果项目里用了多个数据库连接,比如主从库,迁移脚本只跑主库就行,从库自己同步。
说到团队协作,迁移脚本的冲突问题最让人头疼。两个人同时改了模型,各自生成了迁移脚本,合并代码的时候就会撞车。解决办法是,每次pull代码之后,先check一下有没有新的迁移脚本,如果有,手动把两个脚本合并成一个,或者用flask db merge命令。我现在的习惯是,每天开工第一件事就是git pull,然后立刻跑flask db upgrade,确保本地数据库跟得上团队的节奏。这样即使有冲突,也是小范围的,处理起来快。
再聊点进阶用法。Flask-Migrate支持多环境配置,比如开发库、测试库、生产库,你可以通过配置环境变量来切换数据库连接字符串,然后对不同的库执行迁移。但注意,生产库的迁移要格外谨慎,先在staging环境上跑一遍,确认数据无损再上生产。我见过有人直接在生产库上跑upgrade,结果一个删除操作把整张业务表清了,那场面,真叫一个惨烈。
还有个技巧是给迁移脚本写注释。你可能会觉得,这不就是多此一举吗?但几个月后你再回头看,看到一行“这次改动是为了支持新的会员等级功能”,比看一堆代码猜半天强多了。我现在的习惯是,每个迁移脚本开头写一段话,说明这次改动的目的和影响范围,顺便把关联的业务逻辑提一句。这不仅帮自己回忆,也方便后来接手的人。
总结一下,Flask-Migrate的核心价值不是让你少写SQL,而是让数据库结构的变化变得可追踪、可回滚、可协作。你把它当成数据库版本的git来用,心态就对了。别怕生成脚本报错,报错是好事,说明它发现了你没注意到的问题。多看一眼生成的脚本,多写一行downgrade,多留一句注释,这些看似微小的习惯,能帮你省下无数个加班排查的夜晚。项目上线不是终点,数据库会跟着业务一直变,迁移工具就是你的安全带,系好了,才能开得快。


