Django 6.1 was released in August 5, 2026 and introduced the model fetch modes, a new feature that have the potential to protect us from the N+1 queries problem.
This problem occurs when retrieving a collection of objects, and then accessing related objects for each item in that collection. Instead of using a single query to fetch all data, Django will issues one query to fetch the main object and then N additional queries (N is the number of items in the collection) for the related objects not fetched initially. In large dataset, this can create a severe impact in the application’s performance.
With the model fetch modes, we can avoid, or at least reduce the impact of scenarios like that.
As an example, consider an event application that have the following models: Event for the event
details, and Place with information of the location where the event will happen:
|
|
The following code is just an example, but in a real application, it could be a template where we iterate over a list of events to show them in the HTML response, or some background task that perform some processing in a set of events. But to test and understand how the fetch modes works it should be good enough.
|
|
Accessing only attributes of Event model, one single query is executed by Django:
|
|
The problem starts when we try to access attributes of related objects. If
we need to use one property of the Place that relates for each event, depending
how we create the QuerySet and which field fetch mode configured for the model we
can have just one or a very large number of distinct SQL queries which causes serious
performance issues, specially when dealing with a large dataset.
Given the following code, where we get the name of the Event, and the city
of the related Place, in the next section we can compare the behavior and the
SQL queries generated:
|
|
FETCH_ONE
|
|
This is the default behavior, like if we are using .all(). For each row, the
missing field will be fetched. So if we have for example 100 events,
the previous code will execute 101 SQL queries: one to get all events (showed above) and
for each returned event, another query to retrieve the information of the related place.
|
|
The query above will be execute 100 times, with the id on “events_place”.“id” = 6` varying according to
the place ID for each event. Even if multiple events share the same place ID, a new query will be executed multiple times.
.select_related()
The default behavior is the root cause of the well know N+1 queries problem. Django
provides a solution if you use .select_related() method. Instead of multiple
queries, the ORM will join both tables:
|
|
The query will be:
|
|
This is the proper way to avoid the problem, but we can’t forget to call it when creating the QuerySet. Usually this is a hidden bug: we start the project accessing only attributes of the main model, and eventually we start using information from related models and forget to update the QuerySet.
FETCH_PEERS
In addition to the default mode, there is the FETCH_PEERS mode. If our QuerySet
uses it, we will have the initial query to get all events without joining with the places
table, but in the moment we access one of place’s attribute, a new query is executed
to retrieve the missing field for all instances that originally came from the QuerySet.
|
|
Running the code above will execute the following SQL queries:
|
|
It works like a on-demand select related. So instead a “N+1 queries problem”, in the worst case we will have only two queries and we don’t need to worry about which field to prefetch.
FETCH_RAISE
We also have the FETCH_RAISE. In that mode, every time we try to access an attribute not prefetched,
a FieldFetchBlocked exception will be raised. In a performance-critical code where any unexpected
query may be a problem, this fetch mode can prevent unintentional queries.
|
|
Only the first query is executed:
|
|
And when event.place.city is needed, the exception is raised:
|
|
Changing the default fetch mode
You can change the default fetch for a model class using a custom manager that overrides
get_queryset():
|
|
Limitations
Not all “N+1 queries problems” can be solved by chosing a proper fetch mode. Fetch modes only apply to:
-
ForeignKeyfields -
OneToOneFieldfields and their reverse accessors -
Fields deferred with
QuerySet.defer()orQuerySet.only()
So if you have a ManyToManyField like the example:
|
|
And we try to access attributes of the related tags:
|
|
The result is 101 queries (considering that we have 100 events in our database):
|
|
For each event we will have one query as follows:
|
|
In that scenario, the solution is to use .prefetch_related() method, that is similar to .select_related(), but generate
and extra query for each of the related objects that we access:
|
|
And the queries are reduced to only two:
|
|
If you want to start a discussion about this topic, you can send me an e-mail: [email protected] ✉️