All Articles
How-to·5 min read

How to Speed Up Django ORM Queries Without Raw SQL

Most slow Django pages are not a database problem — they are a query problem. Here is how to fix the worst offenders without dropping to raw SQL.

Part of: Python Programming & Development

Most "slow database" complaints in Django are not the database's fault. They are the ORM issuing hundreds of tiny queries where one would do. You can fix almost all of it without writing a single line of raw SQL. Here is how.

Accessing a foreign key in a loop triggers one query per row. select_related performs a SQL join and fetches everything in a single query.

# Slow: 1 query for books, then 1 per book for the author for book in Book.objects.all(): print(book.author.name) # Fast: one JOIN for book in Book.objects.select_related("author"): print(book.author.name)

Use select_related for forward ForeignKey and OneToOne relationships.

Joins do not work for reverse or many-to-many relations. prefetch_related runs a second query and stitches the results together in Python.

for author in Author.objects.prefetch_related("books"): print(author.name, [b.title for b in author.books.all()])

Two queries total, regardless of how many authors you have.

3. Fetch Only What You Need

only() and values() stop the ORM from hydrating full model instances when you only need a few columns.

# Loads every column Book.objects.all() # Loads just two columns as dicts — much less memory Book.objects.values("id", "title")

4. Count and Exist Without Loading Rows

# Loads all rows, then measures the list — wasteful if len(Book.objects.filter(active=True)): ... # Pushes the work to the database if Book.objects.filter(active=True).exists(): ...

Use .exists() for presence checks and .count() for totals.

Common Pitfalls

  • ▸Calling .all() inside a template loop re-runs the query. Prefetch first.
  • ▸select_related on a nullable FK still works but uses a LEFT JOIN — check your query with str(queryset.query).
  • ▸Adding select_related("a", "b", "c") across many tables can produce a giant join. Measure before assuming it is faster.

Summary

Ninety percent of Django ORM performance work is spotting N+1 patterns and reaching for select_related or prefetch_related. Add .only() and .exists() where they fit, and you will rarely need raw SQL at all.

#Python#Django#ORM#Performance

Want to go deeper?

Python Programming & Development

View Course