bigywater.tech
Django · 50

Django Migration 文件与依赖图

migrations 目录里的一堆 0001、0002 文件,Django 是怎么把它们串成执行顺序的?django_migrations 表里存了什么?这篇用三个互相依赖的 app,把迁移文件、依赖、执行计划和已应用记录一层层摊开。

外键 → 依赖
orders.0001 依赖 people.0001,billing.0001 依赖 orders.0001;同 app 的 0002 依赖 0001
图不入库
django_migrations 表只存已应用的迁移名,依赖图每次从文件里重新构建
无环
循环依赖抛 CircularDependencyError

迁移文件与依赖

实测 · 本机执行输出
  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 要靠拆分迁移(先建表、后加外键)打破循环;本文没有演示怎么拆。

分步演示:迁移依赖图

迁移文件与依赖图
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;三个 app 没有预先存在的 migrations 目录,由 makemigrations 生成
    • 没有验证:squashed migrations 在图里的替换(第 58 篇)、run_before、swappable_dependency(AUTH_USER_MODEL)、迁移里的 atomic 属性、跨数据库迁移(router)
    京ICP备2026067502号-1京公网安备11010502063487号