為什麼Django ORM中的N+1問題如此常被提及?
使用Django ORM時,您會經常聽到「N+1問題」這個詞。
但如果沒有親自體驗,很難感受到這個問題有多嚴重。
簡而言之,「本應只執行一次的查詢卻意外執行了過多次」。
結果,頁面加載速度會顯著變慢,且隨著數據增多,性能下降會變得更加嚴重。
特別是在查詢模型之間有關聯的數據時,這種情況經常會出現,所以使用ORM時必須了解它。
💡 N+1問題,讓我們輕鬆理解一下
假設我們在建立一個博客系統。
每個作者(Author)可以撰寫多篇文章(Post)。
用Django模型表示如下:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE)
每個作者(Author)可以撰寫多篇文章(Post),因此是1:N關係。
現在假設我們要實現一個功能,輸出所有作者的名字和他所寫的文章標題。
一般來說,您可能會這樣編寫:
⚠️ 產生N+1問題的代碼
authors = Author.objects.all() # (1) 第一次查詢 (查詢Author表)
for author in authors:
print(author.post_set.all()) # (N) 各Author的Post單獨查詢
執行一次後,加載時間可能會比預期的長。
如果數據不多,可能不會明顯,但如果有100位作者和超過500篇文章呢?
問題隨之而來,漸漸顯露出來。
🧐 實際執行的SQL查詢
SELECT * FROM author; -- 執行1次
SELECT * FROM post WHERE author_id = 1; -- 執行N次
SELECT * FROM post WHERE author_id = 2;
SELECT * FROM post WHERE author_id = 3;
...
隨著作者數量的增加,查詢次數呈指數增長。
這就是N+1問題。
🚀 N+1問題在哪些情況下經常發生?
事實上,無論是否善用Django ORM,
在查詢兩個或更多相關模型的數據時,這個問題幾乎總是必須注意的。
特別是在以下情況下經常發生:
1️⃣ 在模板中循環引用數據的情況
{% for author in authors %}
{{ author.name }}
{% for post in author.post_set.all %}
- {{ post.title }}
{% endfor %}
{% endfor %}
在模板中循環訪問數據時,
每個對象都會產生附加的查詢,從而導致性能下降。
2️⃣ 在視圖函數中循環內部引用相關模型
def blog_view(request):
authors = Author.objects.all() # 第一次查詢 (查詢Author)
author_list = []
for author in authors:
author_list.append({
'name': author.name,
'posts': author.post_set.all() # 產生N個附加查詢
})
return render(request, 'blog.html', {'authors': author_list})
在視圖函數中處理數據時也會出現同樣的問題。
💡 我親身經歷的N+1問題
我曾經製作過一個名為get_absolute_url()
的自定義方法。
這是一個通過組合模型的多個字段生成URL的函數,
在性能變慢的問題出現後我才找到了原因。
class Post(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def get_absolute_url(self):
return f"/author/{self.author.name}/{self.slug}/"
每次調用此方法時,self.author.name
會執行,
並且產生N個附加查詢。
為了解決這個問題,我花了不少時間。
✅ 總結:必須理解N+1問題的原因
- 只要使用Django ORM,這個問題隨時可能發生。
- 如果頁面速度變慢,ORM執行了比預期多的查詢的可能性很高。
- 在模板、視圖函數、模型方法中都可能會發生。
- 數據越多,性能下降就會越嚴重。
- ORM不會自動優化,因此需要手動檢查執行的查詢並進行優化。
✅ 在下一篇文章中,我將介紹如何使用select_related
和prefetch_related
來解決N+1問題。
Add a New Comment