Django ORM: queries optimaliseren voor maximale prestaties
Volledige gids voor het optimaliseren van Django ORM-queries. select_related, prefetch_related, indexen, analyse van het N+1-probleem en geavanceerde technieken voor high-performance applicaties.

De Django ORM biedt een elegante abstractie voor de interactie met de database, maar die eenvoud kan kritieke prestatieproblemen verbergen. Een slecht geoptimaliseerde Django-applicatie kan honderden queries genereren waar één enkele zou volstaan. Deze gids bespreekt de essentiële technieken om die problemen te herkennen en op te lossen.
Meten gaat voor optimaliseren. Met django-debug-toolbar in ontwikkeling worden alle gegenereerde SQL-queries zichtbaar en zijn knelpunten snel op te sporen.
Het N+1-probleem begrijpen
Het N+1-probleem is de meest voorkomende valkuil bij ORMs. Het ontstaat wanneer een eerste query N objecten ophaalt en vervolgens N extra queries worden uitgevoerd om de relaties van elk object te benaderen. Deze vermenigvuldiging van queries verslechtert de prestaties aanzienlijk.
# models.py
from django.db import models
class Author(models.Model):
"""Model dat de auteur van een boek voorstelt."""
name = models.CharField(max_length=200)
email = models.EmailField(unique=True)
bio = models.TextField(blank=True)
def __str__(self):
return self.name
class Book(models.Model):
"""Model dat een boek met zijn auteur voorstelt."""
title = models.CharField(max_length=300)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name='books'
)
published_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
def __str__(self):
return self.titleDeze eenvoudige modellen maken het mogelijk het N+1-probleem concreet te illustreren.
# views.py - Probleemvoorbeeld
def list_books_bad(request):
"""❌ Deze view genereert N+1 queries."""
books = Book.objects.all() # 1 query voor de boeken
for book in books:
# Elke toegang tot book.author triggert een SQL-query
print(f"{book.title} door {book.author.name}")
# Met 100 boeken = 101 SQL-queries!
return render(request, 'books/list.html', {'books': books})De code lijkt onschuldig maar genereert per boek een query om de bijbehorende auteur op te halen.
N+1 oplossen met select_related
De methode select_related voert een SQL-JOIN uit en haalt gerelateerde gegevens op in één enkele query. Ze werkt voor ForeignKey- en OneToOneField-relaties.
# views.py - Geoptimaliseerde oplossing met select_related
def list_books_optimized(request):
"""✅ Deze view genereert één query met JOIN."""
# select_related voert een SQL-JOIN uit
books = Book.objects.select_related('author').all()
for book in books:
# Geen extra query: author is al geladen
print(f"{book.title} door {book.author.name}")
# Totaal: 1 enkele SQL-query, ongeacht het aantal boeken
return render(request, 'books/list.html', {'books': books})De gegenereerde SQL-query gebruikt een LEFT OUTER JOIN om auteurs samen met de boeken op te halen.
# select_related ketenen voor geneste relaties
# models.py
class Publisher(models.Model):
name = models.CharField(max_length=200)
country = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=300)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
# views.py
def list_books_with_details(request):
"""Haalt boeken, auteurs en uitgevers in één query op."""
books = Book.objects.select_related(
'author', # ForeignKey naar Author
'publisher' # ForeignKey naar Publisher
).all()
return render(request, 'books/list.html', {'books': books})Meerdere relaties kunnen tegelijk worden geoptimaliseerd door ze op te sommen in select_related.
ManyToMany-relaties optimaliseren met prefetch_related
Voor ManyToMany-relaties of omgekeerde relaties (ForeignKey vanaf de andere kant) voert prefetch_related aparte maar geoptimaliseerde queries uit en vermijdt daarmee zware joins.
# models.py
class Tag(models.Model):
"""Tags om boeken te categoriseren."""
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=300)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, related_name='books')De ManyToMany-relatie tussen Book en Tag vereist prefetch_related voor een efficiënte optimalisatie.
# views.py - ManyToMany-optimalisatie
def list_books_with_tags(request):
"""✅ Haalt boeken en hun tags efficiënt op."""
books = Book.objects.prefetch_related('tags').all()
for book in books:
# Tags zijn voorgeladen, geen extra query
tag_names = [tag.name for tag in book.tags.all()]
print(f"{book.title}: {', '.join(tag_names)}")
# Totaal: 2 queries (boeken + tags), ongeacht het volume
return render(request, 'books/list.html', {'books': books})prefetch_related voert een aparte query uit voor de tags en voert de join vervolgens in Python uit.
Gebruik select_related voor ForeignKey en OneToOne (SQL-JOIN). Gebruik prefetch_related voor ManyToMany en omgekeerde relaties (aparte queries). Beide kunnen op dezelfde QuerySet worden gecombineerd.
Voorladen aanpassen met Prefetch-objecten
Het Prefetch-object biedt fijnmazige controle over de voorgeladen data: filteren, sorteren en zelfs het aantal resultaten beperken.
# views.py
from django.db.models import Prefetch
def list_authors_with_recent_books(request):
"""Haalt auteurs op met enkel hun recente boeken."""
# Aangepaste Prefetch: alleen boeken vanaf 2025
recent_books_prefetch = Prefetch(
'books',
queryset=Book.objects.filter(
published_date__year__gte=2025
).order_by('-published_date'),
to_attr='recent_books' # Opgeslagen in een aangepast attribuut
)
authors = Author.objects.prefetch_related(
recent_books_prefetch
).all()
for author in authors:
# Toegang via het aangepaste attribuut
for book in author.recent_books:
print(f"{author.name}: {book.title}")
return render(request, 'authors/list.html', {'authors': authors})Het attribuut to_attr slaat de resultaten op in een Python-lijst in plaats van de gebruikelijke manager.
# Geavanceerde combinatie: select_related + Prefetch
def list_authors_complete(request):
"""Volledig voorbeeld van optimalisatie op meerdere niveaus."""
authors = Author.objects.prefetch_related(
Prefetch(
'books',
queryset=Book.objects.select_related(
'publisher' # Optimaliseert ook de uitgever van elk boek
).prefetch_related(
'tags' # En de tags van elk boek
).filter(published_date__year=2026)
)
).all()
return render(request, 'authors/complete.html', {'authors': authors})Deze aanpak verlaagt het aantal queries voor complexe datastructuren drastisch.
Klaar om je Django gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Kolommen beperken met only() en defer()
Standaard haalt Django alle kolommen van de tabel op. Bij modellen met veel velden of grote velden verbetert het beperken van kolommen de prestaties.
# views.py
def list_books_minimal(request):
"""Haalt alleen de noodzakelijke kolommen op."""
# only() bepaalt welke kolommen worden opgenomen
books = Book.objects.only(
'id',
'title',
'published_date'
).select_related('author')
# Let op: toegang tot een niet opgenomen veld triggert een query
for book in books:
print(book.title) # OK, opgenomen
# print(book.isbn) # Zou een extra query veroorzaken
return render(request, 'books/list.html', {'books': books})De methode only() maakt een 'deferred' object aan dat alleen de opgegeven kolommen laadt.
# defer() om specifieke kolommen uit te sluiten
def list_authors_without_bio(request):
"""Sluit grote, zelden gebruikte velden uit."""
# defer() sluit de opgegeven kolommen uit
authors = Author.objects.defer(
'bio' # Het TextField wordt niet geladen
).all()
for author in authors:
print(author.name) # OK
print(author.email) # OK
# author.bio zou het veld op aanvraag laden
return render(request, 'authors/list.html', {'authors': authors})defer() is de tegenhanger van only(): de opgesomde kolommen worden niet initieel geladen.
Optimaliseren met values() en values_list()
Wanneer alleen bepaalde waarden nodig zijn zonder volledige modelobjecten, leveren values() en values_list() lichtere dictionaries of tuples.
# views.py
def get_book_titles(request):
"""Haalt alleen de titels op als een lijst."""
# values_list levert tuples
titles = Book.objects.values_list('title', flat=True)
# Resultaat: ['Boek 1', 'Boek 2', ...]
# values levert dictionaries
book_data = Book.objects.values('title', 'published_date')
# Resultaat: [{'title': 'Boek 1', 'published_date': ...}, ...]
return render(request, 'books/titles.html', {'titles': titles})Deze methoden vermijden het instantiëren van modelobjecten en verlagen het geheugenverbruik.
# Combinatie met aggregaties
from django.db.models import Count, Avg
def get_author_statistics(request):
"""Statistieken per auteur zonder objecten te laden."""
stats = Author.objects.values('name').annotate(
book_count=Count('books'),
avg_year=Avg('books__published_date__year')
).order_by('-book_count')
# Resultaat: [{'name': 'Auteur', 'book_count': 5, 'avg_year': 2024}, ...]
return render(request, 'authors/stats.html', {'stats': stats})Met annotaties worden berekeningen rechtstreeks in de database uitgevoerd.
Indexen aanmaken om queries te versnellen
Database-indexen versnellen zoekopdrachten drastisch. Django laat toe ze rechtstreeks in de modellen te definiëren.
# models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=300, db_index=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
published_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
status = models.CharField(max_length=20, default='available')
class Meta:
# Samengestelde indexen voor frequente queries
indexes = [
# Index op publicatiedatum (frequente sortering)
models.Index(
fields=['published_date'],
name='book_pub_date_idx'
),
# Samengestelde index voor filteren op auteur + status
models.Index(
fields=['author', 'status'],
name='book_author_status_idx'
),
# Gedeeltelijke index: alleen beschikbare boeken
models.Index(
fields=['published_date'],
name='book_available_idx',
condition=models.Q(status='available')
),
]
# Standaardsortering die de index benut
ordering = ['-published_date']Deze indexen verbeteren de prestaties van queries die op deze kolommen filteren.
Indexen versnellen leesbewerkingen maar vertragen schrijfbewerkingen (INSERT, UPDATE) iets. Maak ze enkel aan op kolommen die vaak in WHERE-, ORDER BY- of JOIN-clausules voorkomen.
Gebruikmaken van ruwe SQL-queries indien nodig
Voor complexe queries of databasespecifieke optimalisaties bieden ruwe SQL-queries volledige controle.
# views.py
from django.db import connection
def get_books_with_raw_sql(request):
"""Ruwe query voor speciale gevallen."""
# Methode 1: raw() om modelobjecten op te halen
books = Book.objects.raw('''
SELECT b.*, a.name as author_name
FROM library_book b
INNER JOIN library_author a ON b.author_id = a.id
WHERE b.published_date > %s
ORDER BY b.published_date DESC
''', ['2025-01-01'])
return render(request, 'books/list.html', {'books': books})
def execute_custom_query(request):
"""Directe uitvoering voor niet-SELECT-queries."""
with connection.cursor() as cursor:
# Query met complexe aggregatie
cursor.execute('''
SELECT
a.name,
COUNT(b.id) as book_count,
AVG(EXTRACT(YEAR FROM b.published_date)) as avg_year
FROM library_author a
LEFT JOIN library_book b ON b.author_id = a.id
GROUP BY a.id, a.name
HAVING COUNT(b.id) > 2
ORDER BY book_count DESC
''')
results = cursor.fetchall()
return render(request, 'stats.html', {'results': results})Ruwe queries omzeilen de ORM maar verliezen de databaseportabiliteit.
Queries analyseren met django-debug-toolbar
Met de tool django-debug-toolbar zijn alle SQL-queries van een Django-view zichtbaar.
# settings.py - Configuratie van de debug toolbar
INSTALLED_APPS = [
# ... andere apps
'debug_toolbar',
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
# ... andere middlewares
]
# Toolbar tonen voor lokale aanvragen
INTERNAL_IPS = ['127.0.0.1']
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.sql.SQLPanel', # SQL-queries
'debug_toolbar.panels.timer.TimerPanel', # Uitvoeringstijd
'debug_toolbar.panels.cache.CachePanel', # Cache
]Deze configuratie activeert de panelen die het nuttigst zijn voor optimalisatie.
# Logging van SQL-queries in ontwikkeling
# settings.py
LOGGING = {
'version': 1,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
},
},
}Deze logging toont elke SQL-query in de console en helpt N+1-problemen op te sporen.
QuerySets cachen
Voor data die vaak wordt opgevraagd en zelden verandert, voorkomt caching herhaalde queries.
# views.py
from django.core.cache import cache
from django.views.decorators.cache import cache_page
def list_featured_books(request):
"""Haalt uitgelichte boeken op met cache."""
cache_key = 'featured_books_list'
# Poging om uit de cache te halen
books = cache.get(cache_key)
if books is None:
# Cache miss: query uitvoeren
books = list(
Book.objects.select_related('author')
.filter(featured=True)
.order_by('-published_date')[:10]
)
# 5 minuten in de cache opslaan
cache.set(cache_key, books, timeout=300)
return render(request, 'books/featured.html', {'books': books})
# Decorator om de hele view te cachen
@cache_page(60 * 15) # Cache 15 minuten
def list_all_tags(request):
"""Toont alle tags (zelden gewijzigde data)."""
tags = Tag.objects.annotate(
book_count=Count('books')
).order_by('-book_count')
return render(request, 'tags/list.html', {'tags': tags})De cache verlaagt de databasebelasting voor statische data.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Conclusie
De optimalisatie van Django ORM-queries steunt op een paar fundamentele principes:
✅ Meten vóór optimaliseren: gebruik django-debug-toolbar om reële problemen op te sporen
✅ Het N+1-probleem elimineren: select_related voor ForeignKey, prefetch_related voor ManyToMany
✅ Data beperken: only(), defer(), values() om alleen het nodige te laden
✅ Slim indexeren: indexen aanmaken op vaak gefilterde of gesorteerde kolommen
✅ Strategisch cachen: zelden gewijzigde data profiteert van Django's cache
✅ Ruwe queries: als laatste redmiddel voor databasespecifieke optimalisaties
Methodisch toegepast veranderen deze technieken een trage Django-applicatie in een prestatiegerichte oplossing die grote datavolumes aankan. De Django ORM blijft een krachtig hulpmiddel zodra de subtiliteiten worden beheerst.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Tags
Delen
Gerelateerde artikelen

Django en Python sollicitatievragen: De Top 25 in 2026
De 25 meest voorkomende Django- en Python-sollicitatievragen. ORM, views, middleware, DRF, signals en optimalisatie met gedetailleerde antwoorden en codevoorbeelden.

Django 5: Een REST API bouwen met Django REST Framework
Volledige handleiding voor het bouwen van een professionele REST API met Django 5 en DRF. Serializers, ViewSets, JWT-authenticatie en best practices uitgelegd.

Django Sollicitatievragen: ORM, Middleware en DRF Diepgaand Behandeld
Uitgebreide gids voor Django sollicitatievragen over ORM-optimalisatie, middleware-architectuur en Django REST Framework. Met productie-ready codevoorbeelden voor Django 5.2.