改了模型,数据库为什么能自动跟上?------Django 迁移(migrations)的底层
这篇文章讲 Django migrations 的工作原理:makemigrations/migrate 做了什么、迁移文件的结构、依赖与状态表,以及数据迁移和冲突合并------这是团队协作中避不开的一块。
一次"改了模型,数据库却没变"的翻车
刚学 Django 时,我在模型里加了一个字段,兴冲冲地重启服务,结果一查数据库------报错:"Unknown column 'xxx'"。我这才知道:改了模型,数据库不会自动跟着变,中间还差一步叫"迁移"。
后来我才搞懂,Django 的 migrations 是一套相当精巧的"数据库版本控制"机制:它把你的模型改动,记录成一个个可追溯、可回滚、可合并的"补丁",让数据库 schema 和模型定义始终同步。这篇文章就把它的底层拆开。
为什么需要迁移:模型和数据库是两回事
Django 里,你的数据表结构定义在 models.py(Python 代码),但真正的表结构存在于数据库里。两者不会自动同步------你需要一个"翻译官",把 Python 模型的改动,翻译成数据库能执行的 SQL。
这个"翻译官"就是 migrations。它的工作分两步:
bash
python manage.py makemigrations # ① 对比模型和已有迁移,生成"迁移文件"
python manage.py migrate # ② 执行迁移文件,真正改动数据库
makemigrations 只生成文件、不改数据库 ;migrate 才真正动手改库。记住这个分工,能少踩很多坑。
迁移文件里装了什么
makemigrations 会在应用的 migrations/ 目录下生成形如 0002_add_age_field.py 的文件,里面是一个 Migration 类:
python
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("myapp", "0001_initial")] # 依赖哪个迁移
operations = [
migrations.AddField(
model_name="user",
name="age",
field=models.IntegerField(default=0),
),
]
两个关键部分:
dependencies:这个迁移建立在哪个迁移之上。它决定了迁移的执行顺序,也构成了"迁移链"。operations:要做的具体操作,每个 operation 对应一条或多条 SQL。常见的有:
text
CreateModel / DeleteModel 建表 / 删表
AddField / RemoveField 加字段 / 删字段
AlterField 改字段(类型、默认值等)
RenameField / RenameModel 重命名
RunPython 执行自定义 Python(做"数据迁移")
理解 dependencies 是理解迁移的关键 :迁移不是"快照",而是一条有先后顺序的补丁链,每个补丁都声明"我在谁之后"。
migrate 怎么知道"执行到哪了"
Django 在数据库里维护一张特殊表 django_migrations,记录"哪些迁移已经执行过了":
text
id | app | name | applied
1 | myapp | 0001_initial | 2024-01-01 10:00:00
2 | myapp | 0002_add_age | 2024-01-02 11:00:00
migrate 会对比"磁盘上的迁移文件"和"这张表",只执行那些还没被记录的新迁移。这也解释了为什么迁移能"断点续传"------它靠这张表记住进度。
数据迁移:不光改表结构,还能改数据
schema 迁移改的是"表结构",但有时你还需要改已有数据 。比如把原来的 name 字段拆成 first_name 和 last_name,光加字段不够,还得把存量数据拆开填进去。这时用 RunPython:
python
def split_names(apps, schema_editor):
User = apps.get_model("myapp", "User")
for user in User.objects.all():
first, _, last = user.name.partition(" ")
user.first_name = first
user.last_name = last
user.save()
class Migration(migrations.Migration):
operations = [
migrations.RunPython(split_names), # 纯 Python,写自己的逻辑
]
注意要用 apps.get_model() 拿到历史版本 的模型,而不是直接 from .models import User------因为迁移执行时,模型的代码可能已经又变了。
团队协作的两大坑:冲突与合并
多人开发时,迁移最容易出问题:
- 迁移冲突 :两个人都从
0001生成了各自的0002,合并代码后出现两个"都依赖 0001 的 0002"。解法是用python manage.py makemigrations --merge自动生成一个合并迁移,或手动调整dependencies。 - 手改数据库 :有人图省事直接改了数据库,导致
django_migrations表和实际 schema 不一致,后续迁移全线报错。铁律:永远用迁移改库,不要手改。
几条保命实践
- 迁移文件纳入版本控制,和代码一起提交、一起评审;
- 每次改模型,
makemigrations+migrate两步都做,别只做一半; - 上线前在本地跑一遍迁移 ,尤其是数据迁移(RunPython),确认它可回滚(
migrate myapp 0001能退回去); - 迁移要小步走:一个大迁移不如几个语义清晰的小迁移,出问题好定位。
收个尾
回到开头那个"改了模型数据库却不变"的翻车:那一步之差,就是 makemigrations 和 migrate。Django 的迁移机制,本质是一套**"模型改动 → 补丁文件 → 依赖链 → 进度表"的版本控制系统**------它让数据库 schema 的演进变得可追溯、可回滚、可协作。理解这四点,你就真正会"迁移"了。
Django 迁移建立在扎实的 Python 与 ORM 功底之上,想系统掌握 Django 与 Python,推荐 B站【408实验室】的《Python 完全自学教程》。