Diagnose and fix slow Django ORM queries. Detects N+1s, duplicates, missing indexes, and more -- with exact file:line references and actionable fixes.
Every Django app accumulates hidden query inefficiencies -- N+1 loops behind serializers, duplicate fetches scattered across views, full table scans on unindexed columns. django-query-doctor intercepts queries at runtime using connection.execute_wrapper(), runs them through 8 analyzers, and produces prescriptions with the exact file, line, and code fix. It works in middleware, tests, CI pipelines, and management commands -- no DEBUG=True required.
pip install django-query-doctor# settings.py
INSTALLED_APPS = [..., "query_doctor"]
MIDDLEWARE = [..., "query_doctor.middleware.QueryDoctorMiddleware"]from query_doctor.context_managers import diagnose_queries
with diagnose_queries() as report:
books = list(Book.objects.all())
for book in books:
_ = book.author.name # triggers N+1
assert report.issues > 0
print(f"Found {report.issues} issues in {report.total_queries} queries")Console output (plain-text renderer; Rich adds colors and panels):
============================================================
Query Doctor Report
Total queries: 13 | Time: 0.2ms | Issues: 2
============================================================
CRITICAL: N+1 detected: 12 queries for table "myapp_author" (field: author)
Location: myapp/views.py:12 in list_books
Code: _ = book.author.name # triggers N+1
Fix: Add .select_related('author') to your queryset
Queries: 12 | Est. savings: ~0.2ms
INFO: Fat SELECT: 8 columns from "myapp_book" including large fields: description
Location: myapp/views.py:10 in list_books
Code: books = list(Book.objects.all())
Fix: Use .defer('description') to skip loading large fields, or .values()/.values_list() if you don't need model instances
Queries: 1 | Est. savings: ~0.0ms
| Issue | What It Catches |
|---|---|
| N+1 Queries | Related objects loaded one-per-row in loops |
| Duplicate Queries | Same SQL executed multiple times per request |
| Missing Indexes | Filters on columns without database indexes |
| Fat SELECT | Fetching all columns when only a few are used |
| QuerySet Evaluation | len(qs) instead of qs.count(), bool(qs) instead of qs.exists() |
| Query Complexity | Excessive JOINs, subqueries, or OR chains |
| SerializerMethodField | AST analysis of get_<field> method bodies for hidden N+1s |
Every prescription includes: severity, file:line, and the exact code fix.
QueryTurbo reduces SQL compilation overhead by caching compiled query structures and extracting parameters directly from Django's Query tree, bypassing repeated calls to SQLCompiler.as_sql(). Queries are validated across 3 executions before the compilation step is skipped entirely. Enable it in settings:
QUERY_DOCTOR = {
"TURBO": {"ENABLED": True}
}- Python 3.10+
- Django 4.2, 5.0, 5.1, 5.2, or 6.0
- Optional extras:
pip install django-query-doctor[rich]for styled console output,[celery]for Celery task support,[otel]for OpenTelemetry export - Optional third-party packages (install separately, not query-doctor extras): DRF for serializer analysis, psycopg3 for prepared-statement support
📖 Documentation | 📦 PyPI | 📝 Changelog | 🐛 Issues
MIT