Django Migration 的作用是什么?线上执行迁移要注意什么?
简化版
Django Migration 用来把模型变更转成可追踪、可执行、可回滚的数据库结构变更。开发时用 makemigrations 生成迁移文件,用 migrate 应用迁移。线上要注意备份、兼容发布、避免大表长时间锁表、不要随意改已经上线的迁移文件。
详细版
Migration 的核心作用:
- 记录模型结构变化;
- 生成数据库 DDL;
- 保证不同环境结构一致;
- 支持按依赖顺序执行;
- 支持一定程度的回滚。
常用命令:
python manage.py makemigrations
python manage.py migrate
python manage.py showmigrations
python manage.py sqlmigrate app_name 0001
线上迁移要特别谨慎:
- 先备份数据库;
- 评估大表加字段、建索引、改字段类型的锁表风险;
- 使用向前兼容的灰度发布流程;
- 数据迁移和结构迁移尽量拆开;
- 已经合入并在线上执行过的迁移不要随意修改,应该新增迁移。
完整版教学
一、Migration 解决了什么问题
没有迁移系统时,团队容易遇到这些问题:
- A 同学手动加了字段,B 同学本地数据库没有;
- 测试库和生产库结构不一致;
- 不知道某次发布改了哪些表;
- 回滚版本时不知道数据库怎么处理;
- 多人同时改模型,SQL 文件顺序混乱。
Django Migration 把数据库结构变化变成代码仓库里的文件,让结构变更可以被 review、提交、部署和追踪。
二、makemigrations 和 migrate 的区别
makemigrations 是“生成计划”:
python manage.py makemigrations
它比较模型定义和已有迁移状态,生成新的迁移文件,例如:
app/migrations/0002_article_status.py
migrate 是“执行计划”:
python manage.py migrate
它把未执行的迁移应用到数据库,并在 django_migrations 表里记录执行历史。
面试中很多人会混淆这两个命令。准确说:makemigrations 不一定改数据库,migrate 才真正应用到数据库。
三、迁移文件里有什么
迁移文件通常包含依赖和操作:
class Migration(migrations.Migration):
dependencies = [
("blog", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="article",
name="status",
field=models.CharField(max_length=20, default="draft"),
),
]
它描述“在 Article 上增加 status 字段”。Django 根据数据库后端生成对应 SQL。
四、数据迁移和结构迁移要分清
结构迁移改表结构,数据迁移改已有数据。数据迁移可以用 RunPython:
def fill_status(apps, schema_editor):
Article = apps.get_model("blog", "Article")
Article.objects.filter(status="").update(status="draft")
operations = [
migrations.RunPython(fill_status),
]
注意这里应使用 apps.get_model() 获取历史模型,而不是直接 import 当前 models.py。因为迁移运行时要基于当时的模型状态,直接 import 当前模型可能和旧迁移不匹配。
五、线上迁移的风险
小表迁移通常没问题,大表迁移要非常慎重。例如:
- 给大表加非空字段并带默认值;
- 给大表建索引;
- 改字段类型;
- 删除字段;
- 重命名字段;
- 大批量数据更新。
这些操作可能锁表、拖慢数据库、导致发布窗口过长。更稳妥的做法是兼容式发布:
- 先加可空字段;
- 发布兼容新旧字段的代码;
- 后台分批回填数据;
- 再加约束或切读写;
- 最后清理旧字段。
六、团队协作注意事项
- 迁移文件要提交到 Git;
- 多人同时生成迁移后,要检查依赖冲突;
- 已上线执行过的迁移不要直接修改;
- 可以用
showmigrations查看执行状态; - 可以用
sqlmigrate预览 SQL; - 发布前在测试环境演练迁移。
七、线上迁移为什么要谨慎
Migration 在线上最怕两类问题:锁表和数据不兼容。比如给大表新增非空字段并设置默认值,在某些数据库和版本下可能触发表重写或长时间锁表;修改字段类型、删除字段、重命名字段也可能影响正在运行的旧代码。迁移不是简单执行 python manage.py migrate,而是一次数据库结构发布。
比较稳妥的做法是把危险变更拆成多步。例如新增字段时先允许为空并上线代码写入新字段,后台补齐历史数据,再改成非空约束;删除字段时先让代码不再读取该字段,再执行删除迁移。这样可以降低代码版本和数据库结构短暂不一致带来的风险。
数据迁移也要注意批量规模。RunPython 如果一次性加载所有行再更新,可能造成内存暴涨或长事务。更好的方式是分批处理、使用 iterator()、批量更新,必要时把大数据修复任务放到独立脚本或后台任务中,而不是放在一次迁移里长时间执行。
八、团队协作中的 Migration 冲突
多人同时开发时,两个分支可能都基于同一个 migration 新增了 0005_xxx.py,合并后会出现迁移分叉。Django 可以通过 makemigrations --merge 生成 merge migration,但前提是你要理解两条迁移之间是否有依赖冲突,而不是机械合并。
还有一个常见问题是随意修改已经发布到线上并执行过的 migration 文件。原则上,已经被共享环境执行过的 migration 应该视为历史记录,不要随便改内容;需要变更就新增 migration。否则不同环境的迁移状态可能不一致,后续排查会很痛苦。
面试时可以主动说:Migration 既是 schema 变更工具,也是团队数据库演进历史。开发环境可以重置,生产环境要可追溯、可回滚、可灰度。这个层次比单纯说“makemigrations 生成迁移,migrate 执行迁移”更完整。
九、常见误区与追问
| 命令/概念 | 作用 | 面试风险点 |
|---|---|---|
makemigrations | 根据模型变化生成迁移文件 | 生成不等于已修改数据库 |
migrate | 按依赖执行迁移 | 线上可能锁表或长事务 |
RunPython | 执行数据迁移 | 大表要分批,避免一次性加载 |
- 误区:makemigrations 会直接改数据库。 它只生成 migration 文件;真正执行结构变更的是
migrate。 - 误区:迁移文件可以随便改。 已经被共享环境或线上执行过的迁移应视为历史记录,变更应新增 migration。
- 误区:线上迁移就是发布时跑 migrate。 线上要考虑备份、锁表、兼容发布、回滚预案和数据量。
- 追问:大表加非空字段怎么更稳? 先加可空字段,上线兼容代码,分批回填,再加非空约束,最后清理旧逻辑。
- 追问:多人同时生成 0005 迁移怎么办? 检查依赖和操作是否冲突,必要时用
makemigrations --merge生成合并迁移,而不是机械改文件名。 - 追问:数据迁移为什么不能随便写大循环? 大循环可能造成长事务、锁等待和内存压力,应该分批、限制范围,或拆到后台任务。
记忆钩子:Migration 是数据库结构的版本历史,不只是命令。开发看依赖,线上看风险,大表看锁和分批。
十、加强记忆
Django Migration 是数据库结构变更的版本控制:makemigrations 生成迁移文件,migrate 执行迁移。线上重点不是会敲命令,而是懂风险:备份、预览 SQL、大表锁表、兼容发布、数据迁移拆分、已上线迁移不乱改。