FrankWiles.com
Back to all questions
SJ Sarah J.
October 14, 2025

I've been struggling with optimizing Django ORM queries for a high-traffic dashboard. We use select_related everywhere, but we are still seeing N+1 issues in some background tasks.

Hi Sarah!

While select_related handles foreign keys traversal, it doesn’t handle Many-to-Many relationships (M2M) or reverse lookups, which require prefetch_related.

If you’re debugging background tasks (like Celery workers for example), then you don’t have things like Django Debug Toolbar handy. I recommend logging query counts explicitly in your development environment or writing a test case that fails if the query count exceeds a threshold.

python
from django.test.utils import CaptureQueriesContext
from django.db import connection

def test_dashboard_query_count(self):
    # Set up complex data...

    with CaptureQueriesContext(connection) as ctx:
        response = self.client.get('/dashboard/')

    # Assert we don't exceed expected query count
    self.assertLess(len(ctx.captured_queries), 15)

We do this so much we added a helper to django-test-plus specifically for it.

Another often overlooked tool is using the --print-sql flag if you are running scripts via management commands, or simply inspecting the connection.queries list in a shell session.

Key Takeaways

  1. select_related for ForeignKey and OneToOneField (SQL JOINs)
  2. prefetch_related for ManyToManyField and reverse ForeignKey (separate queries + Python joining)
  3. Test your query counts - don’t guess, measure
  4. Use django-debug-toolbar during development, but also have tests for production code paths

Hope this helps!

View other questions about:
Previous Question
When should I use Django vs Flask vs FastAPI?