Skip to content

python.django.orm_select_related

Performance Medium

Detects ORM queries followed by relationship access in loops without select_related() or prefetch_related().

Django’s ORM uses lazy loading by default:

  • N+1 queries — Each relationship access triggers a new query
  • Database overload — 100 objects with a relationship = 101 queries
  • Slow page loads — Latency compounds with each query
  • Hidden until production — Small datasets hide the problem

This is one of the most common Django performance issues.

# ❌ Before (N+1 queries)
users = User.objects.all()
for user in users:
print(user.profile.avatar) # Query per user!
for order in user.orders.all(): # Another query per user!
print(order.total)

100 users = 1 + 100 + 100 = 201 queries.

# ✅ After (2 queries)
users = User.objects.select_related('profile').prefetch_related('orders')
for user in users:
print(user.profile.avatar) # No extra query
for order in user.orders.all(): # No extra query
print(order.total)
  • QuerySet iteration followed by relationship access
  • Missing select_related for ForeignKey/OneToOne
  • Missing prefetch_related for ManyToMany/reverse FK
  • Nested relationship access patterns

Unfault adds appropriate select_related() or prefetch_related() calls.

# select_related: ForeignKey and OneToOneField
# Uses SQL JOIN - one query total
User.objects.select_related('profile', 'company')
# prefetch_related: ManyToManyField and reverse ForeignKey
# Uses separate query - 2 queries total
User.objects.prefetch_related('orders', 'permissions')
# Combine them
User.objects.select_related('profile').prefetch_related('orders')
# Complex prefetch with filtering
from django.db.models import Prefetch
User.objects.prefetch_related(
Prefetch('orders', queryset=Order.objects.filter(status='pending'))
)