Django ORM

The Django ORM maps Python classes to database tables and Python method chains to SQL. You define models, and Django handles schema migrations, queries, relationships, and transactions across PostgreSQL, MySQL, SQLite, and Oracle. It's one of the most productive parts of Django: most applications never need handwritten SQL.

It's also where most Django performance problems originate. QuerySets are lazy, relationships load on attribute access, and an innocent-looking template loop can fire hundreds of queries. Learning to read the queries the ORM generates, and to shape them with select_related, prefetch_related, annotate, and only, is essential for production apps.

TL;DR

Quick Example

Core Concepts

Models and Fields

Each model subclass maps to a table, and each field to a column with a type and constraints: CharField, IntegerField, DecimalField, DateTimeField, JSONField, ForeignKey, ManyToManyField, and many more. Meta declares ordering, indexes, constraints (UniqueConstraint, CheckConstraint), and table options. Add business methods and properties to models, which is the "fat models" approach, but keep complex workflows in service functions.

QuerySets and Laziness

Order.objects.filter(...) builds a query without running it. Chaining (.filter().exclude().order_by()) refines it. Evaluation happens on iteration, slicing with a step, len(), list(), bool(), or methods like .get(), .first(), .count(), .exists(). A QuerySet caches its results after evaluation, so iterating it twice doesn't query twice, but calling .filter() again creates a new QuerySet and a new query.

Lookups, Q, and F

Double underscores traverse relationships (customer__email) and apply lookups (__gte, __in, __isnull, __date, __year).

Relationships

Always set on_delete deliberately: CASCADE, PROTECT, SET_NULL, or RESTRICT.

select_related vs prefetch_related

This is the ORM equivalent of the batching DataLoader does in GraphQL.

Aggregation and Annotation

Beware of joining across multiple multi-valued relations in one annotate, since each join multiplies rows and inflates sums. Use subqueries (Subquery, OuterRef) instead.

Transactions

By default, each query autocommits. Group related writes:

select_for_update() locks rows for read-modify-write within a transaction. See database transactions.

Performance Toolkit

The Django Debug Toolbar (in development) and APM tools (in production) show query counts and slow queries per request. See query optimization.

Best Practices

Watch Query Counts

Use assertNumQueries in tests for important views and APIs, so N+1 regressions fail CI:

Push Work to the Database

Filtering, counting, summing, and updating in SQL beats loading objects into Python. update() with F() expressions also avoids race conditions in read-modify-write code.

Keep Queries Out of Templates

Templates that traverse relationships ({{ order.customer.name }} in a loop) trigger lazy loads. Prepare QuerySets with the right select_related/prefetch_related in the view.

Use Constraints for Data Integrity

UniqueConstraint, CheckConstraint, and non-null fields enforce rules in the database, where they can't be bypassed by bulk operations or concurrent requests, unlike validation in save().

Common Mistakes

The N+1 Loop

Read-Modify-Write Races

Evaluating QuerySets Unintentionally

if queryset: loads every row to check truthiness, and len(queryset) loads all rows to count them. Use .exists() and .count() when you don't need the objects.

FAQ

What's the difference between select_related and prefetch_related?

select_related follows single-valued relationships (ForeignKey, OneToOne) with a SQL JOIN in the same query. prefetch_related handles multi-valued relationships (reverse ForeignKey, ManyToMany) with a separate query per relation, joined in Python. Use both together as needed.

When should I use raw SQL?

Rarely: for complex reporting queries, database-specific features the ORM can't express, or performance-critical paths where you've measured a benefit. Prefer ORM features first (expressions, subqueries, window functions, RawSQL fragments), then Manager.raw() or connection.cursor() with parameterized queries.

Is the Django ORM slow?

The ORM adds modest overhead per query and per model instance; most "slow ORM" problems are really too many queries or too many rows. With select_related, prefetch_related, values(), proper indexes, and bulk operations, Django applications handle very high traffic.

Can I use the ORM asynchronously?

Django provides async QuerySet methods (aget(), acount(), async for obj in qs) that run safely in async views, though the underlying database drivers are largely still synchronous, so calls run in a thread pool. See async Django.

Related Topics

References