Django N+1 查詢問題
By Elaine Liu
- One minute read - 163 wordsN+1查詢問題
N+1 問題是在 ORM(包括 Django ORM)中常見的效能問題 指的是在處理關聯資料時,除了要執行一次主查詢外,還要為每個結果執行而外查詢
來舉個例子 !
# models.py
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
造成N+1的寫法
def bad_example(request):
# 1 次查詢獲取所有書籍
books = Book.objects.all()
# 對每本書都需要額外查詢一次作者 (N 次查詢)
for book in books:
print(book.author.name) # 這裡會觸發額外的查詢
# 如果有 100 本書,總共會執行:
# 1 次查詢書籍 + 100 次查詢作者 = 101 次查詢
如何解決?
💾 使用select_related
(適用於ForeignKey)
# 一次性獲取書籍及其作者資料
books=Book.objects.select_related('author').all()
for book in books:
print(book.author.name) #不會觸發額外查詢
💾 使用prefetch_related
(適用於ManyToMany)
# models.py
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
# views.py
books = Book.objects.prefetch_related('authors').all()
for book in books:
for author in book.authors.all(): # 不會觸發額外查詢
print(author.name)
偵測N+1問題
使用Django Debug Toolbar
INSTALLED_APPS = [
...
'debug_toolbar',
]
# 查看 SQL 查詢次數和執行時間
最佳實踐
1.善用QuerySet方法
# 正確使用
Book.objects.select_related('author')\
.prefetch_related('categories')\
.filter(published_date__year=2023)
2.避免在循環中查詢
# 錯誤
for book in books:
author_name = book.author.name # 觸發查詢
# 正確
books = Book.objects.select_related('author')
for book in books:
author_name = book.author.name # 不觸發查詢
3.注意查詢優化
# 只取需要的欄位
Book.objects.only('title', 'author__name').select_related('author')
# 排除不需要的欄位
Book.objects.defer('description').select_related('author')
N+1問題如果不處理,會導致:
1.資料庫查詢次數過多
2.應用程式效能降低
3.資料庫負載增加
4.響應時間變慢