migrations 目录里的一堆 0001、0002 文件,Django 是怎么把它们串成执行顺序的?django_migrations 表里存了什么?这篇用三个互相依赖的 app,把迁移文件、依赖、执行计划和已应用记录一层层摊开。
Migrations for 'people':
people/migrations/0001_initial.py
+ Create model Person
Migrations for 'orders':
orders/migrations/0001_initial.py
+ Create model Order
Migrations for 'billing':
billing/migrations/0001_initial.py
+ Create model Invoice
people/migrations/0001_initial.py 的 dependencies = []
orders/migrations/0001_initial.py 的 dependencies = [('people', '0001_initial'),]
billing/migrations/0001_initial.py 的 dependencies = [('orders', '0001_initial'),] Migrations for 'people':
people/migrations/0002_person_email.py
+ Add field email to person
0002 的 dependencies = ('people', '0001_initial'),依赖写在每个迁移的 dependencies 里,由 makemigrations 根据外键自动生成。你也可以手工添加(比如「必须在另一个 app 的某个数据迁移之后」)。
[ ] people.0001_initial [ ] orders.0001_initial [ ] billing.0001_initial [ ] contenttypes.0001_initial [ ] contenttypes.0002_remove_content_type_name [ ] people.0002_person_email
migrate 按依赖图的拓扑排序执行。排序只保证依赖先于被依赖者,其余顺序不要依赖。
只迁移 people 到 0001,再迁移 orders 之后: (1, 'people', '0001_initial') (2, 'orders', '0001_initial') showmigrations: billing [ ] 0001_initial contenttypes [ ] 0001_initial [ ] 0002_remove_content_type_name orders [X] 0001_initial people [X] 0001_initial [ ] 0002_person_email
django_migrations 只是「已应用清单」。所以手工改这张表(或 --fake,第 56 篇)就能让 Django 认为某个迁移已经执行过。
节点数: 6
叶子节点(每个 app 一个): [('billing', '0001_initial'), ('contenttypes', '0002_remove_content_type_name'), ('orders', '0001_initial'), ('people', '0002_person_email')]
billing.0001 的直接依赖: [<Node: ('orders', '0001_initial')>]
billing.0001 的前向计划(需要先执行的所有迁移,含自己): [('people', '0001_initial'), ('orders', '0001_initial'), ('billing', '0001_initial')]
已应用的迁移: [('orders', '0001_initial'), ('people', '0001_initial')]MigrationLoader(connection).graph 是 makemigrations 与 migrate 共用的数据结构:节点是 (app, name),边是依赖。叶子节点用来判断「每个 app 的最新迁移」,多个叶子节点就是迁移冲突(第 57 篇)。
people.0001 依赖 billing.0001 之后 → django.db.migrations.exceptions.CircularDependencyError: orders.0001_initial, billing.0001_initial, people.0001_initial
互相引用的 app 要靠拆分迁移(先建表、后加外键)打破循环;本文没有演示怎么拆。