Django migration原理简述
前阵子面试的时候突然被问到Django的migration原理,还让我有多仔细讲多仔细,面试官明显吃透了这个机制要考考我,当时就跪了。。。抛开半年没有碰django项目不谈,本身对migration背后的原理确实了解不深。当时还有点嗤之以鼻,觉得用的时候或者碰到问题的时候再查就好了,然而抛开不懂知识点面试就得跪不说,在认真了解一下django migration原理后,我发现它实际上是一个十分值得学习的数据库表同步的实现,今天在这里做下记录。
什么是migration
我们知道Django使用了Active Record的架构模式:一个模型对应数据库的一个表。Migration就是一个工具,让用户在对模型进行修改后(如增删改字段),将变化同步应用到数据库对应的表中。本质上就是migration分析models的变化后生成对应的ddl命令,然后应用到数据库中。
migration流程
假设我们创建一个project neko和app nekocollect:
|
|
然后在nekocollect的models.py 中创建一个品种模型breed:
|
|
此时我们要将这个模型同步到mysql中,生成对应的表。只需在neko项目文件夹中运行以下命令:
|
|
此时我们会发现nekocollect文件夹中的migrations文件有新的文件生成:
|
|
里面的内容长这样:
|
|
这个文件主要记录两个信息:dependencies和operations。前者记录本次migration所依赖的migration,由于这是第一次migration,所以为空;后者则记录了本次migration的具体改动,比如这一次就是新增模型Breed和字段name。
接下来我们尝试再添加一个字段,并为name加一个默认值:
|
|
此时我们会发现新的migration文件:
|
|
Behind the scenes
在第二次migration中,django实际上进行了以下操作,这便是所有migration背后的具体流程:
- 在内存中创建一个breed模型的虚拟对象,根据nekocollect的migrations文件夹从0001文件开始,按照文件内的operations对对象进行改动,一直应用到最后的文件,得到当前模型改动前的最新版本
- 对比旧的最新版本与当前修改后的models.py,计算出本次要应用的改动,生成新的migration文件,这就是makemigrations命令做的事情
- migrate命令按照migrations文件生成ddl命令,并应用到数据库中。
然而第三步有个问题:应该从第几个migration文件开始应用?假如数据库是新的,那么从第一个文件开始应用即可,但假如是已经执行过0001文件的数据库呢?进一步考虑,假如在更新数据库之前我们又新增了若干个migrations,怎么让django知道从哪里开始应用?要知道ddl命令不是幂等的,同一个命令执行两遍很有可能会出错。
这里的核心问题是:整套migrations是幂等的,而数据库是有状态的,我们需要知道数据库已经执行了哪些migration,这样才能从未执行的开始。
migrations数据表
为了解决上面这个问题,django会在数据库中创建一个表django_migrations
,里面记录这个数据库应用了哪些migration:
有了这个表,django在migrate的时候便能知道数据库已经执行了哪些migration,接着从未执行的开始应用,这样就不会因重复执行ddl而出错了。
总结
总的来说,django在执行migrate的时候,会根据migrations文件计算出当前未修改的最新模型,然后与修改后的模型进行对比,生成新的migration文件,然后到数据库中,按照django_migrations
表的记录,定位最旧的未应用migration,然后从该文件开始生成并执行ddl语句,当所有migration都应用完,更新django_migrations
表,自此,假如中途没有出错,则数据库表与models.py同步完成。