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:
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:
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.
Sie können auch den Strict-Modus in der Entwicklung aktivieren, um Ausnahmen bei Lazy Loading zu werfen:
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:
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:
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:
Wenn Sie nur Namen und E-Mails brauchen, sagen Sie es:
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:
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:
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:
Hören Sie auf. Ersetzen Sie es durch eine einzige Abfrage:
Das gilt auch für Updates und Deletes:
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:
Für einfache Existenzprüfungen bevorzugen Sie whereIn mit einer Unterabfrage:
Ä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.
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:
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:
9. Verwenden Sie die richtigen Datentypen
Kleine Details sind wichtiger, als Sie denken:
- Verwenden Sie
INT UNSIGNEDfür IDs, nichtVARCHAR. - Verwenden Sie
DECIMALfür Geld, niemalsFLOAT. - Verwenden Sie
ENUModerTINYINTfür Status, nichtVARCHAR(255). - Verwenden Sie
CHAR(36)für UUIDs, nichtVARCHAR(255). - Bevorzugen Sie
DATETIMEoderTIMESTAMPgegenü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:
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:
- Installieren Sie Laravel Debugbar oder Telescope. Sehen Sie, wie viele Abfragen ausgeführt werden.
- Suchen Sie nach N+1-Mustern. Fügen Sie
with()überall hinzu, wo eine Beziehung in einer Schleife zugegriffen wird. - Überprüfen Sie die Abfrageanzahl. Eine Seite sollte typischerweise unter 20 Abfragen ausführen. Über 100 ist ein rotes Tuch.
- Führen Sie EXPLAIN für die langsamsten Abfragen aus. Suchen Sie nach vollständigen Tabellenscans.
- Fügen Sie Indizes hinzu. Passen Sie den Index an die in WHERE und ORDER BY verwendeten Spalten an.
- Cachen Sie, was sich nicht ändert. Besonders Aggregate und Konfigurationsdaten.
- 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.