← Zurück zu den Artikeln
Performance & Datenbanken

Nicht Ihre Datenbank ist langsam – Ihre Abfrage ist es: MySQL-Optimierungsgrundlagen in Laravel

Ein praktischer Leitfaden zur MySQL-Optimierung in Laravel: N+1-Abfragen beheben, die richtigen Indizes hinzufügen, Schleifen vermeiden und eine 3-Sekunden-Seite in 150 ms verwandeln – ohne Hardware-Upgrade.

Verfügbare Sprachen
✦
Ausgewählter Artikel ↗

Nicht Ihre Datenbank ist langsam – Ihre Abfrage ist es: MySQL-Optimierungsgrundlagen in Laravel, die jeder Entwickler kennen sollte

Jeder Laravel-Entwickler hat das schon erlebt. Eine Seite, die früher in 200 ms geladen wurde, braucht plötzlich 3 Sekunden. Der erste Instinkt ist, die Datenbank zu beschuldigen: „MySQL ist langsam", „wir brauchen einen größeren Server", „lass uns auf PostgreSQL umsteigen". Aber hier ist die unbequeme Wahrheit — Ihre Datenbank ist fast nie das Problem. Ihre Abfragen sind es.

MySQL ist eines der am besten erprobten Softwareprodukte, die es gibt. Es kann Millionen von Zeilen und Tausende gleichzeitige Verbindungen ohne Schwierigkeiten bewältigen. Was es nicht kann, ist schlecht geschriebene Abfragen, fehlende Indizes und das berüchtigte N+1-Problem auf magische Weise zu beheben.

In diesem Artikel gehen wir die wesentlichen MySQL-Optimierungstechniken durch, die jeder Laravel-Entwickler kennen sollte — diejenigen, die eine 3-Sekunden-Seite in eine 150-ms-Seite verwandeln, ohne ein einziges Stück Hardware aufzurüsten.

1. Das N+1-Problem: Laravels stiller Killer

Dies ist die häufigste Ursache für langsame Laravel-Anwendungen, und sie ist fast immer unsichtbar, bis man gezielt danach sucht.

Betrachten Sie diesen harmlos aussehenden Code:

<?php $posts = Post::all(); foreach ($posts as $post) { echo $post->author->name; }

Sieht gut aus, oder? Außer dass dies N+1-Abfragen generiert. Eine Abfrage, um alle Posts zu holen, dann eine zusätzliche Abfrage für jeden Post, um dessen Autor zu holen. Bei 100 Posts sind das 101 Abfragen. Bei 1.000 Posts sind das 1.001 Abfragen.

Die Lösung ist Eager Loading:

<?php $posts = Post::with('author')->get(); foreach ($posts as $post) { echo $post->author->name; }

Jetzt sind es nur noch 2 Abfragen, egal wie viele Posts Sie haben.

Wie man N+1-Abfragen erkennt

Laravel hat hervorragende Werkzeuge dafür:

  • Laravel Debugbar — zeigt jede Abfrage und hebt Duplikate hervor.
  • Laravel Telescope — ein vollständiger Debugging-Begleiter mit Abfrage-Profiling.
  • DB::listen() — protokolliert jede Abfrage manuell.
<?php DB::listen(function ($query) { Log::info($query->sql, $query->bindings); });

Sie können auch den Strict-Modus in der Entwicklung aktivieren, um Ausnahmen bei Lazy Loading zu werfen:

<?php // In AppServiceProvider::boot() Model::preventLazyLoading(!app()->isProduction());

Dies zwingt Sie, N+1-Probleme während der Entwicklung zu erkennen, statt in der Produktion.

2. Indizes: Der größte einzelne Gewinn

Wenn Sie nach diesem Artikel nur eine Sache tun, dann diese: fügen Sie Indizes zu Ihren Fremdschlüsseln und häufig abgefragten Spalten hinzu.

Laravel macht dies mit Migrationen einfach:

<?php Schema::table('posts', function (Blueprint $table) { $table->index('user_id'); $table->index('status'); $table->index(['status', 'created_at']); // zusammengesetzter Index });

Eine Abfrage, die 1.000.000 Zeilen scannt, kann mit dem richtigen Index auf das Scannen von 100 Zeilen reduziert werden. Das ist eine 10.000-fache Beschleunigung durch eine einzige Codezeile.

Wie man weiß, welche Indizes man braucht

Verwenden Sie EXPLAIN, um zu sehen, was MySQL tut:

<?php $posts = Post::where('status', 'published') ->where('user_id', 42) ->orderBy('created_at', 'desc') ->get(); // Den Ausführungsplan anzeigen DB::select('EXPLAIN ' . $posts->toSql(), $posts->getBindings());

Achten Sie auf diese Warnsignale:

  • type: ALL — vollständiger Tabellenscan, kein Index verwendet.
  • rows: 500000 — MySQL erwartet, eine halbe Million Zeilen zu scannen.
  • Extra: Using filesort — Sortierung im Speicher oder auf der Festplatte statt über einen Index.
  • Extra: Using temporary — Erstellung einer temporären Tabelle, normalerweise für GROUP BY.

Die Lösung? Fügen Sie einen Index hinzu, der dem Abfragemuster entspricht — normalerweise die Spalten in WHERE, dann die Spalten in ORDER BY.

3. Wählen Sie nur aus, was Sie brauchen

Ein weiterer klassischer Fehler:

<?php $users = User::all(); // holt jede Spalte für jeden Benutzer

Wenn Sie nur Namen und E-Mails brauchen, sagen Sie es:

<?php $users = User::select('id', 'name', 'email')->get();

Das ist wichtiger, als Sie denken. Das Abrufen großer TEXT- oder JSON-Spalten, die Sie nicht verwenden, kann sowohl den Speicherverbrauch als auch die Abfragezeit vervielfachen. Und in Laravel hat jedes Eloquent-Modell Overhead — Hunderte vollständiger Modelle zu erstellen, wenn Sie nur ein paar Felder brauchen, ist verschwenderisch.

Für wirklich schreibgeschützte Daten sollten Sie in Betracht ziehen, Eloquent ganz zu überspringen:

<?php $stats = DB::table('orders') ->select('status', DB::raw('COUNT(*) as total')) ->groupBy('status') ->get();

Query Builder ist deutlich schneller als Eloquent, wenn Sie keine Modelle brauchen.

4. Verwenden Sie chunk() und cursor() für große Datensätze

Eine Million Zeilen in den Speicher zu laden wird Ihre Anwendung zum Absturz bringen — selbst wenn die Abfrage selbst schnell ist. Laravel bietet zwei Werkzeuge für das Streaming großer Datensätze:

<?php // chunk() — lädt in Batches User::chunk(1000, function ($users) { foreach ($users as $user) { // verarbeiten } }); // cursor() — verwendet einen Generator, eine Zeile nach der anderen foreach (User::cursor() as $user) { // verarbeiten }

cursor() verwendet PHP-Generatoren und ist speichereffizient, hält aber eine einzelne Datenbankverbindung für die gesamte Iteration offen. chunk() führt mehrere Abfragen aus, was in der Produktion oft sicherer ist.

5. Vermeiden Sie Abfragen in Schleifen

Wenn Sie sich dabei erwischen, so etwas zu schreiben:

<?php foreach ($userIds as $id) { $user = User::find($id); // ... }

Hören Sie auf. Ersetzen Sie es durch eine einzige Abfrage:

<?php $users = User::whereIn('id', $userIds)->get();

Das gilt auch für Updates und Deletes:

<?php // Schlecht foreach ($userIds as $id) { User::where('id', $id)->update(['active' => false]); } // Gut User::whereIn('id', $userIds)->update(['active' => false]);

Die zweite Version ist eine Abfrage statt Hunderte — oft der Unterschied zwischen 500 ms und 5 ms.

6. Seien Sie vorsichtig mit whereHas und withCount

whereHas() ist praktisch, kann aber teure korrelierte Unterabfragen generieren:

<?php $users = User::whereHas('orders', function ($q) { $q->where('total', '>', 1000); })->get();

Für einfache Existenzprüfungen bevorzugen Sie whereIn mit einer Unterabfrage:

<?php $userIds = Order::where('total', '>', 1000) ->distinct() ->pluck('user_id'); $users = User::whereIn('id', $userIds)->get();

Ähnlich führt withCount() eine Unterabfrage pro Beziehung aus. Das ist in Ordnung für ein paar Beziehungen, aber wenn Sie fünf Beziehungen in derselben Abfrage zählen, sollten Sie eine Umstrukturierung in Betracht ziehen.

7. Cachen Sie aggressiv

Manchmal ist die schnellste Abfrage diejenige, die Sie gar nicht ausführen. Laravels Cache-Schicht ist eines der am meisten unterschätzten Performance-Werkzeuge im Framework.

<?php $topPosts = Cache::remember('top_posts', 3600, function () { return Post::orderByDesc('views')->limit(10)->get(); });

Für Daten, die sich selten ändern — Kategorien, Einstellungen, statische Listen — ist Caching im Wesentlichen kostenlose Performance. Verwenden Sie Redis oder Memcached in der Produktion und denken Sie daran, Schlüssel zu invalidieren, wenn sich Daten ändern.

8. Analysieren Sie langsame Abfragen in der Produktion

MySQL hat ein integriertes Werkzeug zum Auffinden langsamer Abfragen:

-- Slow Query Log aktivieren SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

Nach einem Tag in der Produktion überprüfen Sie das Log. Sie werden die wahren Übeltäter finden — die Abfragen, von denen Sie nie gedacht hätten, dass sie langsam sind, die Tausende Male pro Minute ausgeführt werden.

In Laravel können Sie auch langsame Abfragen pro Anfrage über einen Service Provider protokollieren:

<?php // In AppServiceProvider::boot() if (app()->isProduction()) { DB::listen(function ($query) { if ($query->time > 1000) { // ms Log::warning('Slow query', [ 'sql' => $query->sql, 'time' => $query->time, ]); } }); }

9. Verwenden Sie die richtigen Datentypen

Kleine Details sind wichtiger, als Sie denken:

  • Verwenden Sie INT UNSIGNED für IDs, nicht VARCHAR.
  • Verwenden Sie DECIMAL für Geld, niemals FLOAT.
  • Verwenden Sie ENUM oder TINYINT für Status, nicht VARCHAR(255).
  • Verwenden Sie CHAR(36) für UUIDs, nicht VARCHAR(255).
  • Bevorzugen Sie DATETIME oder TIMESTAMP gegenüber String-Daten.

Kleinere Spalten bedeuten kleinere Indizes, was schnellere Suchvorgänge bedeutet. Es summiert sich.

10. Optimieren Sie Beziehungen und Einschränkungen

Laravels Migrationen machen es einfach, Fremdschlüssel zu definieren:

<?php Schema::table('posts', function (Blueprint $table) { $table->foreignId('user_id')->constrained()->cascadeOnDelete(); });

Fremdschlüssel bewahren nicht nur die Datenintegrität — sie erstellen auch automatisch einen Index, was oft genau das ist, was Sie für JOIN-Performance brauchen.

Ein praktischer Debugging-Workflow

Wenn eine Seite langsam ist, folgen Sie dieser Reihenfolge:

  1. Installieren Sie Laravel Debugbar oder Telescope. Sehen Sie, wie viele Abfragen ausgeführt werden.
  2. Suchen Sie nach N+1-Mustern. Fügen Sie with() überall hinzu, wo eine Beziehung in einer Schleife zugegriffen wird.
  3. Überprüfen Sie die Abfrageanzahl. Eine Seite sollte typischerweise unter 20 Abfragen ausführen. Über 100 ist ein rotes Tuch.
  4. Führen Sie EXPLAIN für die langsamsten Abfragen aus. Suchen Sie nach vollständigen Tabellenscans.
  5. Fügen Sie Indizes hinzu. Passen Sie den Index an die in WHERE und ORDER BY verwendeten Spalten an.
  6. Cachen Sie, was sich nicht ändert. Besonders Aggregate und Konfigurationsdaten.
  7. Messen Sie erneut. Wiederholen Sie, bis die Seite schnell ist.

Fazit

In neun von zehn Fällen bedeutet „die Datenbank ist langsam" tatsächlich „die Abfragen sind langsam". Bevor Sie nach einem größeren Server, einer anderen Datenbank-Engine oder einem Managed-Cloud-Service greifen, verbringen Sie einen Nachmittag damit, Ihre Abfragen zu optimieren. Sie werden fast immer feststellen, dass die Gewinne bereits da sind — sie warten im Code.

Eager Loading, richtige Indizes, das Vermeiden von Schleifen und Caching werden die Mehrheit der Performance-Probleme in Laravel-Anwendungen lösen. Keine dieser Techniken erfordert neue Infrastruktur. Sie erfordern nur Bewusstsein.

Beginnen Sie mit Ihrer langsamsten Seite. Führen Sie ein Abfrage-Log aus. Beheben Sie das N+1. Fügen Sie einen fehlenden Index hinzu. Messen Sie erneut. Dann wiederholen Sie — und beobachten Sie, wie Ihre Datenbank auf magische Weise „schneller wird", ohne dass Sie etwas an ihr ändern.

Technologien & Themen

Artikel-Tags

Keine Artikel entsprechen diesen Filtern.

Haben Sie ein Projekt oder eine Idee zu besprechen?

Lassen Sie uns sprechen ↗