文章目錄
- 一、查詢基礎
- QuerySet 詳解
- 一對多關聯查詢
- 多對多關聯查詢
- 二、N+1查詢問題
- 問題分析
- 檢測方法
- 解決方案
- 三、高級查詢優化
- values()
- values_list()
- values()和values_list()對比
- Q() 對象復雜查詢
- 查看生成的 SQL
- 四、項目實戰
- 場景
- 實戰
一、查詢基礎
QuerySet 詳解
Django 中通過模型類的 Manager 構建 QuerySet 來檢索數據庫對象,其核心特性包括:
- 代表數據庫中對象的集合
- 可通過過濾器縮小查詢范圍
- 具有惰性執行特性(僅在需要結果時才執行 SQL)
常用過濾器
all()
:返回所有對象filter(**kwargs)
:返回滿足條件的對象exclude(** kwargs)
:返回不滿足條件的對象get(**kwargs)
:返回單個匹配對象(無匹配或多匹配會拋異常)- 切片
# 切片操作示例:返回前5個對象(LIMIT 5)
Book.objects.all()[:5]
一對多關聯查詢
假設一個作者可以寫多本書,但每本書只能屬于一個作者。
from django.db import modelsclass 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()# 外鍵關聯Author,級聯刪除,反向查詢名為booksauthor = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')def __str__(self):return self.title
正向查詢(通過外鍵屬性訪問)
b = Book.objects.get(id=2)
b.author # 獲取關聯的Blog對象,查詢數據庫
b.author = some_body # 設置關聯對象
b.save() # 保存更改
使用 select_related()
預加載關聯對象,避免額外查詢
b = Book.objects.select_related().get(id=2)
print(b.author) # 已預加載到緩存,使用緩存,不查詢數據庫
反向查詢(通過關聯管理器)
# 未定義related_name, 默認Manager名稱為:<模型名稱小寫>_set
a = Author.objects.get(id=1)
a.book_set.all() # 返回所有關聯的Book# 定義了related_name='books'
a.books.all() # 更直觀的訪問方式
關聯對象操作方法如下。所有 “反向” 操作對數據庫都是立刻生效,保存到數據庫。
add(obj1, obj2)
:添加關聯對象create(**kwargs)
:創建并關聯新對象remove(obj1, obj2)
:移除關聯對象clear()
:清空所有關聯set(objs)
:替換關聯集合
a = Author.objects.get(id=1)
a.books.set([b1, b2]) # b1 和 b2 都是 Book 實例
多對多關聯查詢
假設一個作者可以寫多本書,一本書也可以有多個作者。
from django.db import modelsclass Author(models.Model):name = models.CharField(max_length=100)email = models.EmailField()def __str__(self):return self.nameclass Book(models.Model):title = models.CharField(max_length=200)publication_date = models.DateField()# 多對多關聯Authorauthors = models.ManyToManyField(Author, related_name='books')def __str__(self):return self.title
正向與反向查詢示例
# 正向查詢
b = Book.objects.get(id=3)
b.authors.all() # 獲取所有關聯的Author
b.authors.count()
b.authors.filter(name__contains="張三")# 反向查詢
a = Author.objects.get(id=5)
a.book_set.all() # 獲取所有關聯的Book
多對多關聯中,add()、set() 和 remove() 可直接使用主鍵
a = Author.objects.get(id=5)
a.book_set.set([b1, b2])
# 等價于
a.book_set.set([b1.pk, b2.pk])
二、N+1查詢問題
問題分析
N+1 查詢是常見的性能問題,表現為主查詢后執行 N 次額外查詢。例如:
books = Book.objects.all()
for book in books:print(book.author.first_name)
以上代碼會產生 1 次查詢獲取所有 Book,加上 N 次查詢獲取對應的 Author(N 為 Book 數量),共 N+1 次查詢。
檢測方法
- Django Debug Toolbar:直觀顯示請求中的 SQL 查詢
- 日志記錄:配置日志記錄 SQL 語句
- 性能分析工具:如 Django Silk 分析查詢性能
解決方案
方法 1:使用 select_related
適用于一對多(正向)和一對一關系,通過 SQL JOIN 預加載關聯對象
- 語法:
select_related('related_field')
,related_field
是模型中定義的ForeignKey
或OneToOneField
字段
books = Book.objects.select_related('author').all()
for book in books:print(book.author.first_name) # 無額外查詢
可結合 only()
選擇需要的字段
books = Book.objects.select_related('author').only('title', 'author__name')
支持多級關聯
# 加載書籍、作者及作者家鄉信息
books = Book.objects.select_related('author__hometown').all()
for book in books:print(book.author.hometown.name) # 無額外查詢
方法 2:使用 prefetch_related
適用于多對多和反向關系,通過批量查詢后在 Python 中關聯。適用場景:
- 多對多關系(ManyToManyField)
- 反向一對多關系
- 反向一對一關系
books = Book.objects.prefetch_related('authors').all()
for book in books:print(book.authors.all()) # 無額外查詢
參考資料:Django 數據庫訪問優化
三、高級查詢優化
values()
返回字典形式的查詢集(返回一個 ValuesQuerySet
對象,其中每個元素是一個字典),適合提取特定字段
books = Book.objects.values('title', 'author')
for book in books:print(book) # 輸出示例
{'title': 'Book1', 'author': 'Author1'}
{'title': 'Book2', 'author': 'Author2'}
values_list()
返回元組形式的查詢集(返回一個 ValuesListQuerySet
對象,其中每個元素是一個元組),內存占用更低
books = Book.objects.values_list('title', 'author')
for book in books:print(book)### 輸出示例
('Book1', 'Author1')
('Book2', 'Author2')
使用 flat=True
獲取單一字段值列表。如果有多個字段時,傳入 flat
會報錯。
titles = Book.objects.values_list('title', flat=True)
# <QuerySet ['紅樓夢', '西游記', ...]>
使用 named=True
,結果返回 namedtuple()
books_info = Book.objects.values_list("id", "title", named=True)
# <QuerySet [Row(id=1, title='紅樓夢'), ...]>
values()和values_list()對比
對比維度 | values() | values_list() |
---|---|---|
返回值類型 | 返回一個包含字典的查詢集,字典的鍵為字段名,值為字段對應的數據 | 返回一個包含元組的查詢集,元組中的元素依次對應指定字段的值 |
內存占用 | 相對較高,因為字典需要存儲鍵值對信息 | 通常更節省內存,元組是更輕量的數據結構,無需存儲字段名 |
使用場景 | 適合需要通過字段名訪問字段值的場景,例如需要明確知道每個值對應的字段時 | 適合僅需要獲取字段值的場景,例如只需批量獲取某個或某幾個字段的具體數據時 |
Q() 對象復雜查詢
Q()
對象用于構建復雜查詢條件,支持邏輯運算
&
:邏輯與(AND)|
:邏輯或(OR)~
:邏輯非(NOT)
from django.db.models import Q# 標題含Python或作者為John的書籍
books = Book.objects.filter(Q(title__icontains="Python") | Q(author="John")
)# 復雜組合條件
books = Book.objects.filter((Q(title__icontains="Python") | Q(title__icontains="Django")) &~Q(author="John")
)
查看生成的 SQL
調試時可查看 QuerySet 生成的 SQL
queryset = Book.objects.filter(author="John")
print(queryset.query) # 輸出對應的SQL語句
四、項目實戰
場景
Django+Vue 后臺管理系統中,一般需要支持不同的數據權限
- 僅本人數據權限
- 本部門及以下數據權限
- 本部門數據權限
- 指定部門數據權限
- 全部數據權限
數據權限與功能權限(基于RBAC實現)的區別
- 功能權限:控制 “能做什么”(如新增、刪除按鈕的顯示和執行)
- 數據權限:控制 “能看到什么數據”(如銷售經理只能查看自己團隊的數據)
實戰
使用Q() 對象構建復雜查詢,實現靈活的數據權限計算
點擊查看完整代碼
您正在閱讀的是《Django從入門到實戰》專欄!關注不迷路~