58 lines
1.8 KiB
Markdown
58 lines
1.8 KiB
Markdown
# ANTI-PATTERN: N+1 Queries in Django ORM
|
|
|
|
## KONTEXT
|
|
Django/DRF API-Entwicklung, insbesondere bei Listen-Endpoints mit Related Objects.
|
|
|
|
## WAS IST PASSIERT?
|
|
```python
|
|
# SCHLECHT: N+1 Query Problem
|
|
def get_matches(request):
|
|
matches = Match.objects.filter(scenario_id=scenario_id)
|
|
return Response([{
|
|
'id': m.id,
|
|
'home_team': m.home_team.name, # Query für jedes Match!
|
|
'away_team': m.away_team.name, # Noch eine Query!
|
|
'venue': m.home_team.venue.name # Und noch eine!
|
|
} for m in matches])
|
|
```
|
|
|
|
Bei 100 Matches: 1 + 100 + 100 + 100 = **301 Queries** statt 1-4.
|
|
|
|
## WARUM WAR ES SCHLECHT?
|
|
- **Performance:** Exponentieller Anstieg der DB-Queries mit Datenmenge
|
|
- **Latenz:** Jede Query hat Overhead (Netzwerk, Parsing, Locking)
|
|
- **DB-Last:** Unnötige Belastung der Datenbank
|
|
- **Skalierung:** Funktioniert in Dev (10 Records), bricht in Prod (10.000 Records)
|
|
|
|
## DIE BESSERE ALTERNATIVE
|
|
```python
|
|
# GUT: Optimierte Queries
|
|
def get_matches(request):
|
|
matches = Match.objects.filter(
|
|
scenario_id=scenario_id
|
|
).select_related(
|
|
'home_team',
|
|
'away_team',
|
|
'home_team__venue', # Nested relation
|
|
)
|
|
return Response([{
|
|
'id': m.id,
|
|
'home_team': m.home_team.name,
|
|
'away_team': m.away_team.name,
|
|
'venue': m.home_team.venue.name
|
|
} for m in matches])
|
|
```
|
|
|
|
Bei 100 Matches: **1 Query** (mit JOINs).
|
|
|
|
## ERKENNUNGSREGELN
|
|
- `select_related()` für ForeignKey / OneToOneField
|
|
- `prefetch_related()` für ManyToMany / Reverse ForeignKey
|
|
- Django Debug Toolbar zeigt Query-Anzahl
|
|
- `django-query-counter` als Middleware
|
|
|
|
## CHECKLISTE
|
|
- [ ] Hat jeder Listen-Endpoint `select_related`/`prefetch_related`?
|
|
- [ ] Werden verschachtelte Relations berücksichtigt?
|
|
- [ ] Ist die Query-Anzahl bei >100 Records akzeptabel?
|