Django模型關(guān)系:從一對多到多對多全解析
一、一對多關(guān)系: ForeignKey
一對多是最常見的模型關(guān)系,例如 "作者 - 書籍" 場景:假設(shè)一個作者可以寫多本書,但每本書只能屬于一個作者。
定義關(guān)系
核心參數(shù)說明:
on_delete=models.CASCADE:當(dāng)作者被刪除時,關(guān)聯(lián)的書籍也會被自動刪除related_name='books':定義反向查詢名稱,可通過author.books.all()獲取作者的所有書籍
from django.db import models
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Book(models.Model):
title = models.CharField(max_length=100)
publication_date = models.DateField()
# 外鍵關(guān)聯(lián)Author,級聯(lián)刪除,反向查詢名為books
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name='books'
)
def __str__(self):
return self.title
數(shù)據(jù)操作示例
創(chuàng)建數(shù)據(jù)
# 創(chuàng)建作者
author1 = Author.objects.create(first_name='J.K.', last_name='Rowling')
author2 = Author.objects.create(first_name='George', last_name='Orwell')
# 創(chuàng)建書籍并關(guān)聯(lián)作者
book1 = Book.objects.create(
title='Harry Potter',
publication_date='1997-06-26',
author=author1
)
book2 = Book.objects.create(
title='1984',
publication_date='1949-06-08',
author=author2
)
查詢操作
# 正向查詢:通過書籍找作者
book = Book.objects.get(title='1984')
print(book.author) # 輸出: George Orwell
# 反向查詢:通過作者找書籍
author = Author.objects.get(last_name='Rowling')
for book in author.books.all():
print(book.title) # 輸出: Harry Potter
高級配置
禁用外鍵約束:當(dāng)需要靈活管理關(guān)聯(lián)關(guān)系(如允許刪除存在關(guān)聯(lián)數(shù)據(jù)的主表記錄)時,可關(guān)閉數(shù)據(jù)庫級約束
author = models.ForeignKey(
Author,
on_delete=models.SET_NULL,
related_name='books',
db_constraint=False, # 不創(chuàng)建數(shù)據(jù)庫外鍵約束
null=True
)
自定義數(shù)據(jù)庫列名:默認(rèn)會生成<ClassName>_id列,可通過db_column修改
dept_id = models.ForeignKey(
"SystemDept",
on_delete=models.SET_NULL,
db_column="dept_id", # 顯式指定數(shù)據(jù)庫列名
null=True
)
二、多對多關(guān)系: ManyToManyField
多對多關(guān)系適用于 "作者 - 書籍" 的另一種場景:假設(shè)一個作者可以寫多本書,一本書也可以有多個作者。
定義關(guān)系
Django 會自動創(chuàng)建中間表(默認(rèn)名為appname_book_authors)存儲關(guān)聯(lián)關(guān)系,無需手動定義。
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
publication_date = models.DateField()
# 多對多關(guān)聯(lián)Author
authors = models.ManyToManyField(Author, related_name='books')
def __str__(self):
return self.title
數(shù)據(jù)操作示例
添加 / 移除關(guān)聯(lián)
# 創(chuàng)建實例
author1 = Author.objects.create(name='Alice', email='alice@example.com')
author2 = Author.objects.create(name='Bob', email='bob@example.com')
book = Book.objects.create(title='Example Book', publication_date='2023-01-01')
# 添加關(guān)聯(lián)
book.authors.add(author1, author2)
# 移除關(guān)聯(lián)
book.authors.remove(author1)
查詢操作
# 正向查詢:書籍的所有作者
book = Book.objects.get(title='Example Book')
for author in book.authors.all():
print(author.name)
# 反向查詢:作者的所有書籍
author = Author.objects.get(name='Bob')
for book in author.books.all(): # related_name='books'
print(book.title)
自定義中間表
當(dāng)需要存儲關(guān)聯(lián)關(guān)系的額外信息(如邀請原因、加入時間)時,可自定義中間表
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(Person, related_name="invites", on_delete=models.CASCADE)
invite_reason = models.CharField(max_length=64) # 額外信息
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through="Membership", # 指定中間表
through_fields=("group", "person"), # 關(guān)聯(lián)字段
)
三、性能優(yōu)化技巧
select_related:用于一對多關(guān)系,提前加載關(guān)聯(lián)對象,減少數(shù)據(jù)庫查詢
# 普通查詢(N+1問題)
entries = Entry.objects.all()
for entry in entries:
print(entry.blog.name) # 每次循環(huán)都會觸發(fā)新查詢
# 優(yōu)化后(僅1次查詢)
entries = Entry.objects.select_related('blog').all()
for entry in entries:
print(entry.blog.name) # 使用緩存數(shù)據(jù)
批量操作:利用update()進行批量更新,避免循環(huán)操作
# 批量標(biāo)記站內(nèi)信為已讀
SystemNotifyMessage.objects.filter(
id__in=ids.split(",")
).update(
read_status=True,
read_time=timezone.now()
)
四、關(guān)于是否使用外鍵約束
在實際項目中,是否使用數(shù)據(jù)庫外鍵約束需要權(quán)衡利弊
使用外鍵的優(yōu)勢
- 數(shù)據(jù)完整性:數(shù)據(jù)庫級別的約束保證關(guān)聯(lián)數(shù)據(jù)一致性
- 開發(fā)效率:ORM 自動處理關(guān)聯(lián)查詢和級聯(lián)操作
- 查詢便捷:支持
select_related等優(yōu)化方法,簡化多表查詢
禁用外鍵的場景
- 高并發(fā)系統(tǒng):外鍵會增加數(shù)據(jù)庫鎖競爭,影響寫入性能
- 分布式架構(gòu):分庫分表環(huán)境下,跨庫外鍵無法生效
- 復(fù)雜遷移:避免循環(huán)依賴導(dǎo)致的遷移失敗問題
折中方案:使用db_constraint=False 參數(shù)
- 數(shù)據(jù)庫層面:無外鍵約束,數(shù)據(jù)庫不會強制校驗關(guān)聯(lián)數(shù)據(jù)的存在性
- Django ORM 層面:保留邏輯關(guān)聯(lián),ORM仍將字段視為外鍵關(guān)系(邏輯關(guān)聯(lián)),支持 ORM 查詢、操作語法
| 特性 | db_constraint=True (默認(rèn)) |
db_constraint=False |
|---|---|---|
| 數(shù)據(jù)庫外鍵約束 | 創(chuàng)建,強制數(shù)據(jù)一致性 | 不創(chuàng)建 |
| 級聯(lián)操作 | 數(shù)據(jù)庫自動處理 | 僅由 Django ORM 處理 |
| 關(guān)聯(lián)數(shù)據(jù)存在性校驗 | 數(shù)據(jù)庫強制校驗 | 不校驗(需應(yīng)用層保障) |
| ORM 查詢支持 | 完整支持 | 完整支持(邏輯外鍵保留) |
| 性能影響 | 外鍵約束帶來額外開銷 | 無約束開銷 |
| 適用場景 | 強數(shù)據(jù)一致性需求 | 高頻寫入/跨庫/歷史數(shù)據(jù)遷移 |
五、多對多關(guān)系實戰(zhàn)
實戰(zhàn)場景:在一個后臺管理系統(tǒng)中,用戶與角色往往是多對多關(guān)系。一個用戶可以分配多個角色,一個角色也可以屬于多個用戶。

模型定義:點擊查看完整代碼
class SystemUsers(BaseModel, AbstractBaseUser):
id = models.BigAutoField(primary_key=True, db_comment="用戶ID", help_text="用戶ID")
username = models.CharField(
max_length=30, unique=True, db_comment="用戶賬號", help_text="用戶賬號"
)
# ...
# 與角色多對多關(guān)系
roles = models.ManyToManyField(
"SystemRole",
through="SystemUserRole",
through_fields=("user_id", "role_id"),
related_name="users",
)
# ...
class SystemUserRole(BaseModel):
"""用戶和角色關(guān)聯(lián)中間表"""
id = models.BigAutoField(primary_key=True, db_comment="id")
user_id = models.ForeignKey(
"SystemUsers",
on_delete=models.CASCADE,
db_constraint=False,
db_column="user_id",
db_comment="用戶ID",
)
role_id = models.ForeignKey(
"SystemRole",
on_delete=models.CASCADE,
db_constraint=False,
db_column="role_id",
db_comment="角色ID",
)
class Meta:
managed = True
db_table = "system_user_role"
db_table_comment = "用戶和角色關(guān)聯(lián)表"
ordering = ["-id"]
system_user_role數(shù)據(jù)庫生成的中間表

您正在閱讀的是《Django從入門到實戰(zhàn)》專欄!關(guān)注不迷路~

本文詳解Django模型關(guān)系:一對多(ForeignKey)及多對多(ManyToManyField)關(guān)系的定義、操作與優(yōu)化技巧。同時探討外鍵約束的使用場景與權(quán)衡策略。
浙公網(wǎng)安備 33010602011771號