N+1 xảy ra khi bạn chạy 1 query lấy N object, rồi với mỗi object lại chạy thêm 1 query để lấy related → tổng 1 + N query.
posts = Post.objects.all() # 1 query
for p in posts:
print(p.author.full_name) # +1 query mỗi vòng — N query!Fix bằng select_related (FK/OneToOne) hoặc prefetch_related (M2M, reverse FK) — xem [[#9207]]:
posts = Post.objects.select_related('author') # 1 query JOINBắt sớm trong dev có 3 cách: django-debug-toolbar (panel "SQL" hiện tất cả query của 1 request, có cảnh báo duplicate), django.db.connection.queries trong shell sau khi DEBUG=True, và viết test self.assertNumQueries(2): quanh code path quan trọng — fail ngay khi N+1 sinh thêm query.
N+1 trong template rất dễ lọt qua review — {% for p in posts %}{{ p.author.name }} cũng tính. Khi serializer DRF nested mà queryset gốc không prefetch, mỗi item trong list lại phát sinh thêm 2-3 query. Luôn test với dataset ≥ 50 row, không phải 2-3 row — bug N+1 chỉ "nhìn thấy" được khi data đủ lớn.