Veröffentlicht am
28.4.2026
n + 1 Queries - die versteckten Bremsklötze
Das n + 1 Problem stammt ursprünglich aus der Mathematik.
n + 1 Queries gehören zu den lästigsten Übeltätern, die die Performance einer Anwendung in Mitleidenschaft ziehen. Diese treten häufig in ORMs auf, wenn man eine Liste von Datensätzen abruft und für jeden dieser Datensätze eine weitere verknüpfte Tabelle abfragt, ohne das Eager Loading zu verwenden. Dabei ist es völlig egal, ob man Hibernate, Eloquent oder Doctrine verwendet. Sobald sich diese Queries in der gesamten Anwendung auf einzelne Bereiche verteilt haben und entsprechend summieren, entsteht eine lange Ladezeit. Noch schlimmer ist es, wenn sich diese Queries in asynchrone Prozesse eingeschlichen haben. Das heißt, dass der Prozess erst nach einiger Zeit im Hintergrund läuft und der Entwickler davon zunächst nichts mitbekommt. Im schlimmsten Fall kann dieser Query den Arbeitsspeicher überlaufen lassen und den gesamten Prozess zum Absturz bringen. Einige Entwickler gehen dem einfach aus dem Weg, indem sie dem Server, auf dem die Anwendung läuft, einfach mehr Arbeitsspeicher zuweisen. Fehler! Denn wenn sich die Prozesse weiter summieren und eines Tages Größen von Gigabyte annehmen, sind die technischen Schulden sehr stark angewachsen und die darauffolgende Analyse kann das ganze Team zurückwerfen. Insbesondere bei kritischen Enterprise-Anwendungen sollte das Problem sehr ernst genommen und daher vermieden werden.
Beispiel
An einem einfachen Beispiel aus einer Tennisvereinsverwaltung soll dieses Problem in Laravel aufgezeigt werden. Betrachte die folgende Beziehung:
// Team.php
public function coach()
{
return $this->belongsTo(Coach::class);
}
Ein Team gehört zu einem Coach (Beziehung: belongsTo). Der n + 1 Query wird dann ausgelöst, wenn man im Controller alle Teams lädt und in der View über die Teams iteriert:
// TeamController.php
public function index(): View
{
$teams = Team::all(); // SELECT * FROM teams;
return view ('teams.index', compact('teams'));
}
In der Blade-View sieht das dann so aus:
@foreach($teams as $team)
{{ $team->name }}: {{ $team->coach->last_name}}
@endforeach
Der Query aus der TeamController.php lädt alle n Teams. Bei einer großen Enterpriseanwendung könnten das auch Kundendaten oder Bestandsdaten aus dem Lager sein. Wenn ich beispielsweise 10 Teams habe, feuert Eloquent für jedes einzelne Team einen Query ab und es ergeben sich somit 11 Queries (einmal der initiale Query von oben und einmal die weiteren Queries SELECT * FROM coaches WHERE id = [1 bis n]). Wenn wir nun von einer Enterprise-Anwendung ausgehen und von 100.000 oder mehr Kundendaten sprechen, dann verlangsamt sich die Anwendung massiv.
Lösung: Eager Loading
Du kannst Eloquent direkt anweisen, mithilfe von with() die benötigten Daten (hier: Trainer) sofort mitzuladen. Noch besser ist es, wenn du dabei direkt prüfst, welche Attribute von dem Trainer überhaupt geladen werden sollen. Das nennt sich Column Selection und macht die Abfrage deutlich effizienter:
// TeamController.php
public function index(): View
{
/*
* Das Attribut der Zurodnung des Fremdschlüssels (hier: id)
* muss immer enthalten sein, sonst kann Eloquent das
* nicht zuordnen und es kommt zu einem Fehler.
*/
$teams = Team::with('trainer:id,first_name,last_name')->get();
return view ('teams.index', compact('teams'));
}
Damit verkürzt sich die Anzahl von N + 1 Queries genau auf 2 Queries. Je nach Fall kann sich aber auch das in der Einleitung beschriebene Lazy Loading lohnen, wenn beispielsweise Daten abgefragt werden, die nur selten benötigt werden oder eine unvorhersehbare Menge annehmen. Im Zweifel sollte das getestet werden, aber man fährt mit Eager Loading bei kleinen überschaubaren und auch häufig abgefragten Daten deutlich besser.