1. 遷移機制與底層原理
在 Django 中,ORM(Object-Relational Mapping)是連接模型(Model)和數據庫結構的橋梁。Django 鼓勵開發者通過編寫 Python 類(模型)來定義業務數據結構,而不是手動創建數據庫表。當模型發生變化時,如何將這些變化安全地同步到數據庫?這就是 遷移系統(Migration System) 的職責。
Django 提供了兩個核心命令來實現這一機制:
-
makemigrations
:自動檢測模型變更并生成遷移文件 -
migrate
:將遷移文件中的變更應用到數據庫中
什么是遷移(Migration)?
“遷移”是 Django ORM 用來管理數據庫 schema(結構)變化的機制。它的核心思想是:
以版本化腳本的形式記錄模型的每次變更,并確保這些變更可以一步步地、按順序、安全地同步到數據庫結構中。
每一個遷移文件(如 0001_initial.py
)都表示一次變更的“快照”和“指令集”,包含:
-
變更內容(如新增字段、刪除模型)
-
依賴關系(如依賴某次遷移)
-
可執行的操作序列(operations)
遷移常見命令
命令 | 說明 | 應用場景 |
---|---|---|
python manage.py makemigrations | 自動檢測模型變更并生成新的遷移文件(記錄變更指令) | 模型新增字段、修改字段、刪除模型等操作后使用 |
python manage.py migrate | 執行遷移文件中的操作,應用到數據庫 | 本地或生產環境數據庫更新結構時使用 |
python manage.py showmigrations | 顯示所有 app 的遷移文件及其執行狀態 | 檢查當前遷移執行情況,調試遷移問題 |
python manage.py sqlmigrate <app> <migration> | 查看某次遷移將執行的 SQL 語句 | 審查實際執行的 SQL,避免意外刪除數據 |
python manage.py migrate <app> zero | 將指定 app 的所有遷移回滾(還原到未遷移狀態) | 重置數據庫結構,僅用于測試或初始化 |
python manage.py migrate <app> <migration_name> | 回滾或遷移到某個特定遷移版本 | 控制遷移步驟,手動回滾或追蹤問題 |
python manage.py makemigrations --empty <app> | 生成一個空遷移文件(無自動檢測變化) | 手動編寫遷移(如 RunPython 、RunSQL )的入口 |
python manage.py makemigrations --merge | 合并多個沖突的遷移文件 | 分支合并后出現遷移沖突時使用 |
python manage.py migrate --fake <app> <migration> | 標記某個遷移為“已執行”,但不真正執行操作 | 數據庫已手動修改,跳過實際執行,僅記錄狀態 |
python manage.py migrate --fake-initial | 如果數據庫中已有初始結構,跳過初始遷移操作 | 數據庫結構存在但未記錄遷移時用于初始化 |
--fake和--fake-initial區別:
-
--fake
:標記遷移為已執行,但不實際執行數據庫操作。常用于手動修改數據庫結構后,同步 Django 記錄。 -
--fake-initial
:只對初始遷移(initial)文件起作用。若數據庫中已存在對應表結構,會跳過執行遷移,僅標記為已遷移。
--fake
是無條件標記為已遷移,--fake-initial
是檢測到表已存在時標記為已遷移,不重復創建表,如果表不存在,照常創建表
遷移命令背后的流程
makemigrations
工作流程
-
檢測模型變化:Django 會加載當前所有模型定義,和上一次遷移文件中的“歷史模型狀態”進行對比。
-
生成變更指令:根據差異生成操作列表(如
AddField
,RemoveField
,AlterField
等)。 -
保存為遷移文件:把這些操作寫入新的遷移文件中,路徑通常是:
yourapp/migrations/000X_*.py
Django 會在遷移文件中維護一個 state_operations
來表示“模型結構”,用于后續比對。
migrate
工作流程
-
讀取數據庫中已執行的遷移記錄:Django 會讀取特殊表
django_migrations
-
找出需要執行的遷移:即本地 migrations 文件夾中存在,但數據庫未記錄執行的遷移
-
逐個執行:遷移文件中的
operations
會被逐條執行(如 ALTER TABLE),并記錄到django_migrations
django_migrations
表的作用
該表是 Django 用于記錄“數據庫已完成哪些遷移”的關鍵數據結構,字段包含:
字段名 | 說明 |
---|---|
app | 應用名 |
name | 遷移文件名(不含后綴) |
applied | 應用時間 |
這張表 并不代表數據庫當前結構,它僅僅記錄哪些遷移已被執行。數據庫結構的真實狀態取決于這張表 + 遷移腳本 + 數據庫執行結果的三者一致性。?
遷移底層的 Operation
系統
每個遷移文件的 operations
是一個指令列表,繼承自 django.db.migrations.operations.base.Operation
,常見子類包括:
-
CreateModel
:創建模型 -
AddField
/RemoveField
:字段變更 -
AlterField
:字段屬性修改 -
RunPython
:執行自定義 Python 代碼(如數據遷移) -
RunSQL
:執行原生 SQL(如重命名表等)
這些操作通過 MigrationExecutor
進行調度和執行,內部依賴 SchemaEditor
進行跨數據庫后端兼容處理。
2. 常見錯誤分類與解決方案
Django 的遷移系統雖然強大,但在實際開發中,尤其是多人協作、頻繁模型變更的場景中,遷移相關的報錯比較常見。以下是開發中出現的問題及解決建議記錄:
? 1.?table already exists
表已存在
django.db.utils.OperationalError: (1050, "Table 'xxx' already exists")
常見場景?
你嘗試運行migrate,報錯說某張表已經存在。這通常出現在以下幾種開發/部署場景中?
-
你手動在數據庫中創建過表(如寫了原生 SQL)
-
你還原了生產環境數據庫結構,但本地仍執行初始遷移
-
某個 app 的遷移記錄丟失或未提交,但數據庫中的表已經存在
問題本質:
Django 遷移系統默認假設自己會“從無到有”創建所有表。如果你跳過了創建記錄、手動建了表或還原了舊數據,它在執行 CreateModel
操作時會檢測到表已存在,從而報錯。
換句話說:數據庫中已經有表了,但
django_migrations
表中沒有記錄遷移已執行。?
? 解決方案:使用 --fake-initial
Django 為此場景提供了專門參數:
python manage.py migrate --fake-initial
含義是:如果數據庫中已經存在初始表結構,就跳過初始遷移文件的執行,但仍然將遷移記錄寫入 django_migrations
,以免后續遷移報錯。
僅適用于“數據庫中已有表結構”且“你確定結構與模型一致”的情況。
? 2.?is applied before its dependency
遷移歷史不一致
錯誤信息:
?django.db.migrations.exceptions.InconsistentMigrationHistory:
Migration <app>.<migration_name> is applied before its dependency <other_app>.<migration_name> on database 'default'.
常見場景:
將測試環境或生產環境中的完整數據庫備份(含數據和表結構)導入本地開發環境后,執行
migrate
命令時拋出該錯誤。
本地和遠程環境的遷移文件可能一致,但數據庫中 django_migrations
表中的記錄已經“走在前面”或出現不一致。
錯誤本質:
Django 通過 django_migrations
表記錄哪些遷移已經在數據庫中執行過。
但在某些情況下,如從測試環境復制數據庫后:
-
數據庫結構已經更新到較新的狀態(如執行了
0005_auto_xxxx
) -
本地代碼中的遷移文件還停留在較早狀態(如只有到
0003
)
當你在本地執行 migrate
時,Django 會發現 數據庫中“先執行”了某個遷移,但它依賴的遷移在本地尚未執行,于是拋出 InconsistentMigrationHistory
異常。
通用解決方案(推薦用于開發/測試環境)
清空遷移記錄,讓 Django 以
--fake
的方式重新記錄當前狀態,而不實際執行數據庫操作
步驟如下:
-
清空
django_migrations
表:?? 此操作不會影響業務數據,僅清除遷移記錄。
-- MySQL / PostgreSQL:
TRUNCATE TABLE django_migrations;-- SQLite:
DELETE FROM django_migrations;
-
確保本地代碼遷移文件完整、和數據庫結構對應:
? 推薦:使用與數據庫導出時相同的 Git 分支代碼,并保留
migrations/
目錄。 -
重新 fake 所有遷移(跳過實際執行,僅登記記錄):?
python manage.py migrate --fake
🔐 注意事項:
-
不要在生產環境這樣操作,否則遷移記錄丟失,后續無法增量執行遷移。
-
清空
django_migrations
后,數據庫和模型之間的狀態需完全一致,否則你跳過的是未正確應用的變更,可能引發更嚴重的問題。?
? 3. non-nullable field without a default
添加非空字段時執行makemigrations報錯?
錯誤信息:
You are trying to add a non-nullable field 'xxx' to <Model> without a default
?常見場景:
在已有數據的模型中新增一個字段,未設置null(默認為null=False
)且未指定默認值:
# 原有模型
class Product(models.Model):name = models.CharField(max_length=100)# 修改后(會報錯)
class Product(models.Model):name = models.CharField(max_length=100)status = models.IntegerField() # 新增字段未指定 default
原因分析:
數據庫中的舊數據沒有該字段,Django 不知道用什么值填充現有記錄。?
解決方案:?
方法一:設置默認值
status = models.IntegerField(default=0)
方法二:允許為空
status = models.IntegerField(null=True, blank=True)
方法三:在遷移時指定默認值(Django 提示時輸入)?
方法四:使用 RunPython
手動填充舊數據,再移除默認值(推薦用于正式項目)?
? 4. Duplicate column name
字段重復導致migrate失敗
錯誤信息:?
django.db.utils.OperationalError: (1060, "Duplicate column name 'xxx'")
常見場景:
遷移文件被誤刪或修改,模型與數據庫結構不一致,重新執行遷移導致字段重復。
原因分析:
數據庫中已有字段,但遷移記錄(django_migrations
)中并未記錄此字段的創建,Django 嘗試重新創建列。
解決方案:
-
核對數據庫中實際結構和模型定義,確認字段是否已存在
-
避免手動修改數據庫結構;如果修改,必須確保遷移狀態同步
-
使用
migrate --fake
標記遷移已執行,跳過實際操作:????????
python manage.py migrate yourapp 0001 --fake
? 5. Unknown column 'xxx' in 'field list'
刪除字段后未清理數據依賴導致遷移失敗?
錯誤信息:
django.db.utils.OperationalError: (1054, "Unknown column 'xxx' in 'field list'")
常見場景:
刪除模型字段后,項目代碼或數據遷移腳本中仍有對該字段的引用。
原因分析:
字段已從模型中移除,但數據庫中仍存在引用字段的舊代碼或舊 SQL。
解決方案:
-
全局搜索項目代碼中是否還有
xxx
字段的使用 -
若使用
RunPython
寫了數據遷移腳本,需改為先清數據后刪字段 -
對于依賴字段的查詢邏輯,也要在遷移之前調整邏輯
? 6. conflicting migrations
多人協作導致makemigrations沖突
錯誤信息:
Conflicting migrations detected; multiple leaf nodes
常見場景:
兩名開發者分別在不同分支對同一個 app 做了遷移,合并后造成遷移分叉。
原因分析:
遷移文件是線性依賴的,兩個遷移文件若沒有互相引用,就形成沖突。
解決方案:
使用 --merge
合并遷移:
python manage.py makemigrations --merge
Django 會生成一個新的遷移文件,合并兩個沖突遷移(開發者需判斷操作順序)。?