gotchapythondjangoMajor
Django ORM N+1 query problem — select_related vs prefetch_related
Viewed 0 times
Django 3.2+
N+1select_relatedprefetch_relatedORMperformanceJOINqueries
Problem
Accessing related objects in a loop causes one query per object (N+1 problem). for post in Post.objects.all(): print(post.author.name) fires 1 query for posts + N queries for authors.
Solution
Use select_related() for ForeignKey/OneToOne (single JOIN query) and prefetch_related() for ManyToMany or reverse ForeignKey (two queries with Python-side joining).
# BAD: N+1 queries
posts = Post.objects.all()
for post in posts:
print(post.author.name) # Query per iteration
# GOOD: select_related for FK (single JOIN)
posts = Post.objects.select_related('author').all()
for post in posts:
print(post.author.name) # No extra query
# GOOD: prefetch_related for M2M or reverse FK
posts = Post.objects.prefetch_related('tags', 'comments').all()
for post in posts:
print([t.name for t in post.tags.all()]) # No extra query
# Chaining: both at once
posts = Post.objects.select_related('author').prefetch_related('tags')Why
select_related does a SQL JOIN in the initial query — works for FK/O2O where there's one related object. prefetch_related executes a separate query for related objects and joins them in Python — required for M2M where JOINs would produce duplicate rows.
Gotchas
- select_related on a nullable FK includes NULL rows — use select_related('author') not filter(author__isnull=False) if you want all posts
- prefetch_related is invalidated if you filter the queryset after prefetch — use Prefetch() object with queryset= for filtered prefetch
- Calling .all() on a prefetched M2M manager re-queries the DB — it does NOT use the prefetch cache
- Django Debug Toolbar or django-silk can reveal N+1 issues in development
Context
Django views or serializers that iterate over querysets with related objects
Revisions (0)
No revisions yet.