← Zurück zum Blog
Django Performance 15. Mai 2026 · 12 min Lesezeit

Django ORM Performance-Optimierung:
N+1, select_related und Indexes

Inhalt
  1. Das N+1-Problem verstehen
  2. select_related für ForeignKey
  3. prefetch_related für ManyToMany
  4. only() und defer() — nur was gebraucht wird
  5. Datenbankindizes richtig einsetzen
  6. Queries debuggen mit django-debug-toolbar
  7. Fazit

Das N+1-Problem ist einer der häufigsten Performance-Killer in Django-Projekten — und gleichzeitig einer der am einfachsten zu behebenden. Ich begegne ihm regelmäßig in Code-Reviews und bei Projekten, die ich neu übernehme. In diesem Artikel zeige ich dir, wie du es erkennst, behebst und von vornherein vermeidest.

Das N+1-Problem verstehen

Stell dir vor, du hast ein Blog-System mit Autoren und Artikeln. Du willst eine Liste aller Artikel mit dem jeweiligen Autorennamen anzeigen:

# ❌ Das N+1-Problem — ineffizient!
articles = Article.objects.all()

for article in articles:
    # Für jeden Artikel wird eine separate DB-Query ausgeführt!
    print(article.author.name)

Was passiert hier? Django lädt zunächst alle Artikel mit einer Query. Für jeden Artikel wird dann beim Zugriff auf article.author eine weitere Query ausgeführt. Bei 100 Artikeln sind das also 101 Queries — 1 für die Liste, 100 für die Autoren. Das ist das N+1-Problem.

Faustregel: Wenn du in einer Schleife auf eine verwandte Relation zugreifst, hast du wahrscheinlich ein N+1-Problem.

select_related löst das Problem durch einen SQL JOIN — statt N+1 Queries gibt es genau eine:

# ✅ Korrekt mit select_related
articles = Article.objects.select_related('author').all()

for article in articles:
    # Kein DB-Zugriff mehr — author wurde bereits mitgeladen
    print(article.author.name)

# Mehrere Relationen und Tiefe:
articles = Article.objects.select_related(
    'author',
    'author__profile',
    'category'
)

select_related funktioniert für ForeignKey und OneToOneField. Es erzeugt einen SQL JOIN, was bedeutet: Es ist am effizientesten wenn die verwandten Objekte immer benötigt werden und die Datenmenge überschaubar ist.

Für ManyToManyField und umgekehrte ForeignKey-Relationen ist prefetch_related die richtige Wahl:

# Artikel mit Tags (ManyToMany) und Kommentaren (reverse FK)
articles = Article.objects.prefetch_related(
    'tags',
    'comments',
    'comments__author'
)

for article in articles:
    # Alle Tags und Kommentare bereits im Cache
    tags = article.tags.all()
    comments = article.comments.all()

prefetch_related führt separate Queries aus (eine pro Relation) und verknüpft die Ergebnisse in Python. Mit Prefetch lässt sich die Teilquery noch weiter einschränken:

from django.db.models import Prefetch

# Nur genehmigte Kommentare laden
articles = Article.objects.prefetch_related(
    Prefetch(
        'comments',
        queryset=Comment.objects.filter(approved=True).select_related('author'),
        to_attr='approved_comments'
    )
)

for article in articles:
    # Direkter Zugriff auf das gefilterte Ergebnis
    print(article.approved_comments)

only() und defer() — nur laden was gebraucht wird

Wenn du nur bestimmte Felder benötigst, musst du nicht die gesamte Zeile laden:

# Nur id, title und created_at laden
articles = Article.objects.only('id', 'title', 'created_at')

# Oder umgekehrt: alles AUSSER dem body-Feld (ggf. sehr groß)
articles = Article.objects.defer('body', 'raw_html')

Vorsicht: Wenn du auf ein deferred field zugreifst, wird doch eine Query ausgeführt. Deshalb am besten mit values() oder values_list() arbeiten wenn du wirklich nur Rohdaten brauchst:

# Sehr effizient: gibt Dictionaries zurück
Article.objects.values('id', 'title')

# Noch effizienter: gibt Tupel zurück
Article.objects.values_list('id', 'title', flat=False)

# Einzelnes Feld als flache Liste
ids = Article.objects.values_list('id', flat=True)

Datenbankindizes richtig einsetzen

Selbst perfekte Queries können langsam sein wenn die richtigen Indizes fehlen. Django macht es einfach, Indizes direkt im Model zu definieren:

class Article(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    status = models.CharField(max_length=20)
    created_at = models.DateTimeField(auto_now_add=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

    class Meta:
        indexes = [
            # Einfacher Index für häufige Filterung
            models.Index(fields=['status']),
            # Zusammengesetzter Index für kombinierte Filter
            models.Index(fields=['status', 'created_at']),
            # Index mit Name für spätere Migration
            models.Index(fields=['author', 'category'], name='article_author_cat_idx'),
        ]

ForeignKey-Felder bekommen automatisch einen Index. Setze Indizes auf Felder die häufig in filter() , order_by() oder JOIN-Bedingungen vorkommen.

Queries debuggen mit django-debug-toolbar

Ohne Sichtbarkeit in die tatsächlichen Queries tappt man im Dunkeln. Installiere django-debug-toolbar für die Entwicklung:

# settings.py
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
INTERNAL_IPS = ['127.0.0.1']

Alternativ kannst du Queries direkt in der Django-Shell analysieren:

from django.db import connection, reset_queries
from django.conf import settings

settings.DEBUG = True
reset_queries()

# Dein Code hier:
articles = list(Article.objects.select_related('author').all())

print(len(connection.queries), "Queries")
for q in connection.queries:
    print(q['sql'])

Fazit

Die meisten Django Performance-Probleme lassen sich mit drei einfachen Regeln lösen:

  1. Immer select_related() für ForeignKey/OneToOne verwenden wenn die verwandten Daten gebraucht werden
  2. prefetch_related() für ManyToMany und reverse Relations
  3. Indizes auf Felder setzen die häufig gefiltert oder sortiert werden

Und vor allem: Queries messen, nicht raten. Django Debug Toolbar oder connection.queries zeigen dir genau was passiert.

Yevhen Chubchyk
Yevhen Chubchyk
Senior Python / Django Entwickler · Freelancer seit 2016 · 20+ Jahre IT-Erfahrung. Arbeitet für Kunden im DACH-Raum, darunter Siemens Healthineers und Engie Deutschland.