Analyze and optimize Django ORM queries including N+1 problems, missing indexes, slow queries, and migration issues. Use when troubleshooting slow API responses, database performance, query optimization, or migration errors.
Analyzes and optimizes Django ORM queries and database performance.
Symptom: Many small queries instead of one efficient query
# BAD - N+1 queries (1 + N queries)
projects = Project.objects.all()
for project in projects:
print(project.user.username) # Each access = 1 query!
# GOOD - select_related for ForeignKey/OneToOne
projects = Project.objects.select_related('user').all()
# GOOD - prefetch_related for ManyToMany/reverse FK
projects = Project.objects.prefetch_related('tools', 'technologies').all()
Check serializers for nested data:
# If serializer includes nested user data:
class ProjectSerializer(serializers.ModelSerializer):
user = UserSerializer() # Needs select_related!
# View must optimize:
class ProjectViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return Project.objects.select_related('user')
# If filtering frequently on a field, add index:
class Project(models.Model):
slug = models.SlugField(db_index=True) # Has index
is_private = models.BooleanField(db_index=True) # Add index!
Already configured - check bottom of page in development.
# settings.py - enable query logging
LOGGING = {
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
}
}
}
# Django shell
from django.db import connection, reset_queries
from django.conf import settings
settings.DEBUG = True
reset_queries()
# Run your query
list(Project.objects.all())
print(f"Queries: {len(connection.queries)}")
for q in connection.queries:
print(q['sql'][:100])
# See query execution plan
print(Project.objects.filter(is_private=False).explain())
# Single JOIN query
Project.objects.select_related('user', 'user__profile')
# Separate query, joined in Python
Project.objects.prefetch_related('tools', 'likes')
# Only load needed fields
Project.objects.only('id', 'title', 'slug')
# Exclude heavy fields
Project.objects.defer('description', 'content')
# Returns dicts, not model instances
Project.objects.values('id', 'title')
# Returns tuples
Project.objects.values_list('id', 'title')
from django.db.models import Count, Avg
# Do counting in database, not Python
Project.objects.annotate(like_count=Count('likes'))
# BAD - N queries
for project in projects:
project.views += 1
project.save()
# GOOD - 1 query
Project.objects.filter(id__in=ids).update(views=F('views') + 1)
# Bulk create
Project.objects.bulk_create([Project(...), Project(...)])
docker compose exec web python manage.py showmigrations
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate --fake app_name 0001
docker compose exec web python manage.py squashmigrations app_name 0001 0010
core/
├── projects/
│ ├── models.py # Project model, indexes
│ ├── views.py # QuerySet optimization
│ └── serializers.py # Nested serializers need optimization
├── users/
│ └── models.py # User model
└── tools/
└── models.py # Tool model
config/
└── settings.py # DATABASE config, DEBUG
# Connect to database
docker compose exec db psql -U postgres -d allthriveai
# List tables
\dt
# Describe table
\d core_project
# Show indexes
\di
# Analyze slow query
EXPLAIN ANALYZE SELECT * FROM core_project WHERE is_private = false;
# Show running queries
SELECT pid, query, state FROM pg_stat_activity WHERE state != 'idle';
# In model
class Project(models.Model):
class Meta:
indexes = [
models.Index(fields=['is_private', 'created_at']),
models.Index(fields=['user', 'is_private']),
]
# Or on field
is_private = models.BooleanField(default=False, db_index=True)
Then run:
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate