Django's ORM sits between your application logic and your database, translating Python into SQL and objects back into rows. It handles querysets, joins, aggregations, annotations, subqueries, conditional expressions, and raw SQL fallbacks. The abstraction is good enough that many engineers never look at the generated SQL, which is exactly how N+1 queries, missing indexes, and unintended full table scans slip into production. This hub collects the ORM guides, optimization patterns, and database engineering advice that address those real-world concerns.
The guides below cover query optimization, PostgreSQL-specific tuning, migration strategy, and the data validation patterns that keep your models honest. If you are looking for the broader framework picture, the framework overview maps how the ORM fits into Django's architecture. If you are thinking about deployment-side database concerns like connection pooling and failover, check the deployment hub.
ORM and data guides
Core ORM concerns
Django querysets are lazy by design. They do not hit the database until you iterate, slice, or explicitly evaluate them. This is a feature when you are composing complex queries, and a trap when you are not tracking where evaluation happens. A queryset passed into a template looks like a simple variable, but it triggers a database round-trip every time it is iterated. If that queryset accesses related objects, each access can trigger additional queries.
The fix is almost always select_related for foreign key relationships and prefetch_related for many-to-many and reverse relationships. These are not optimizations you add later. They should be part of your initial queryset construction whenever you know you will access related data. The query optimization guide covers the decision tree for choosing between them.
Indexing is the other half of database performance. Django's db_index field option and Meta.indexes let you declare indexes in your models, but knowing which indexes to create requires understanding your query patterns. Covering indexes, partial indexes, and GIN indexes for full-text search are PostgreSQL features that Django exposes through its ORM. Using them well requires looking at EXPLAIN ANALYZE output and understanding what sequential scans versus index scans actually mean for your query performance.
Common ORM mistakes
- Evaluating querysets in templates without select_related or prefetch_related, causing hidden N+1 queries.
- Adding database indexes after performance problems appear instead of designing them alongside the schema.
- Using .all() when .only() or .defer() would avoid loading unnecessary columns on large tables.
- Writing raw SQL for queries that the ORM handles natively, creating maintenance burden without performance benefit.
- Ignoring migration squashing, which leads to migration chains that take minutes to apply in CI.
What to read next
Start with the query optimization guide if you are working on performance. For PostgreSQL-specific tuning, the PostgreSQL production guide covers connection pooling, indexing, and monitoring. If you are dealing with data integrity concerns, the forms and validation guide addresses validation architecture. For caching as a complement to query optimization, see the caching patterns guide in the performance hub.