Ladezeit verringern mit Laravel – Teil 3: Datenbankabfragen optimieren mit Eloquent

Bei der Ladezeit von Laravel Anwendungen gibt es viele potenzielle Störfaktoren – aber bei datenbankgetriebenen Seiten ist auch hier wieder ein ganz alltäglicher Punkt wichtig: Optimierung von Datenbankabfragen. Nachdem wir uns im letzten Artikel bereits um das Caching mit Redis gekümmert haben geht es nun darum, was wir unternehmen können, um unsere Datenbankabfragen mit Eloquent schnell zu halten.

Was ist Eloquent?

Eloquent ist Laravels hauseigener ORM (Object Relational Mapper). Anstatt mit der Datenbank in normalem SQL zu kommunizieren und so jedes mal wieder aufwendige SELECT oder INSERT Befehle zu schreiben definieren wir einfach verschiedene Klassen, die das für uns übernehmen.

Machen wir ein einfaches Beispiel anhand der User Klasse, die von Laravel bereits mitgeliefert wird. Diese definiert, dass sich ihre Daten in der Tabelle users befinden. Eine Instanz der Klasse User stellt also einen Datensatz aus der Tabelle users dar.

Mit dieser Definition können wir jetzt anstatt einer SQL Query einfach den Befehl

verwenden um den User mit der ID 5 aus der Datenbank aus- und in ein Objekt einzulesen. Und das macht ja auch Sinn, schließlich ist der größte Teil unserer SQL Befehle immer dasselbe – Schlüsselwörter wie SELECT, FROM, WHERE etc. Die wirklich wichtigen Teile wie zum Beispiel der Tabellenname (den wir in der User Klasse finden) und die ID (die der Methode find übergeben wird) machen nur einen geringen Teil aus.

Auch andere Teile einer Query können wir mit Methoden wie select, groupBy oder where einfach und vor allem auch dynamisch abbilden, ohne uns selbst darum zu kümmern, am Ende einen validen SQL String zu haben.

Das nimmt uns natürlich sehr viel Arbeit ab und funktioniert auch bei einzelnen Tabellen sehr gut. Doch meistens bestehen unsere Anwendungen ja aus miteinander verbundenen Datensätzen, zum Beispiel hat ein User dann vielleicht auch noch Bestellungen die ihm zugeordnet werden müssen.

Auch für diesen Fall bietet uns Eloquent einige Hilfsmittel an doch natürlich kann nicht jeder Spezialfall behandelt werden – hier müssen dann wir aktiv werden. Welche Fallstricke gibt es also bei der Arbeit mit Eloquent und wie können wir diese umgehen?

 

 

Komplexe Queries mit raw() schreiben

Wie schon erwähnt ist die Arbeit mit Eloquent zwar wesentlich angenehmer (finde ich zumindest), komplexe Queries sollten aber damit nicht abgebildet werden. Dafür haben wir die Möglichkeit, auch komplett eigene Queries zu schreiben, anstatt uns auf den Query Builder zu verlassen.

Um eigene Queries anzuwenden stehen uns entweder Methoden wie select, insert, update und delete der DB Facade zur Verfügung. Diese verwenden wir zum Beispiel so:

Das heißt also ganz simpel schreiben wir zuerst die Query und können dann als zweiten Methodenparameter noch die einzelnen Queryparameter (dargestellt durch das Fragezeichen) befüllen.

Oder wir haben noch die Möglichkeit, einfach die statement Methode zu verwenden. Diese funktioniert grundsätzlich genau gleich, macht sich aber besser bei Befehlen die vielleicht kein Ergebnis erwarten (wenn wir beispielsweise eine MySQL Tabelle verändern wollen o.ä.).

Haben wir also keine direkte Verknüpfung zwischen unserem MySQL Befehl und unseren Models oder wollen wir einfach wirklich komplexe SQL Queries abbilden bietet sich diese Möglichkeit an.

 

 

Eager Loading

Ein weiterer wichtiger Punkt sind Daten, die zueinander in Beziehung stehen. Oft möchten wir aus der Datenbank nicht nur die Daten eines Models auslesen sondern vielleicht auch gleich dazugehörige Daten.

Nehmen wir mal ein simples Beispiel. Wir haben zwei Models – User und Post. Ein User hat dabei mehrere Posts, ein Post gehört aber zu genau einem User – eine 1:n Beziehung also. Wir möchten jetzt eine Tabelle darstellen, in der alle Posts inklusive des dazugehörigen Users dargestellt werden.

In den Models würde das mit einer hasMany() Beziehung im User Model abgebildet bzw mit einer belongsTo() Beziehung im Post Model.

Nun bestünde ja einerseits die Möglichkeit, einfach alle Posts auszulesen und dann in der Schleife einfach den dazugehörigen User auszulesen. Die „Magie“ hinter Eloquent sorgt dafür, dass wir damit immer alle Daten zur Verfügung haben die wir benötigen. Zum Beispiel so:

Ein sehr vereinfachtes Beispiel.
Das Problem an der Sache sehen wir nur dann, wenn wir uns ansehen, was Laravel „hinter den Kulissen“ macht. Für dieses Beispiel führt Eloquent nämlich nun einerseits eine Query aus, um alle unsere Posts auszulesen (Post::all()) und dann bei jedem Schleifendurchgang eine Query um den dazu passenden User auszulesen ($posts->user->name).

Nehmen wir als Beispiel wir hätten zehn Posts von zehn verschiedenen Usern. Dann würden also im Hintergrund elf Queries ausgeführt (die erste plus eine für jeden Schleifendurchgang) nur um diese Daten aus der Datenbank auszulesen obwohl wir doch schon im Voraus wüssten, dass wir diese bräuchten.

 

 

Und genau hier kommt das sogenannte Eager Loading ins Spiel. Mit dieser Technik teilen wir Eloquent quasi mit Hey, bitte hol mir diese Posts aus der Datenbank aber ich werde gleich auch noch die dazugehörigen User brauchen – lade diese doch bitte auch direkt mit. Und wie funktioniert das? Ganz simpel mit der with() Methode.

Dadurch reduzieren wir die Anzahl der Queries, denn was passiert nun?

Eloquent führt zuerst die Query aus, die alle Posts aus der Datenbank ausliest. Hierbei ändert sich nichts. Doch als zweites wird nun eine Query ausgeführt, die alle User aus der Datenbank ausliest, die zu einem der eben gefundenen Posts passt. Diese User werden anschließend zwischengespeichert und müssen innerhalb der foreach Schleife nun nicht mehr aus der Datenbank gelesen werden.

In unserem Beispiel mit zehn Posts von zehn Usern hätten wir also die Anzahl der nötigen Queries von elf auf gerade mal zwei reduziert. An den richtigen Stellen sorgt allein diese Änderung für einen gewaltigen Boost der Geschwindigkeit, denn nicht oder schlecht optimierte Datenbankqueries sind definitiv einer der größten möglichen Flaschenhälse für unsere Anwendung.

 

 

Lazy Eager Loading

Das führt jetzt natürlich noch zu einem Sonderfall. Nehmen wir an, wir möchten unsere Posts aus der Datenbank laden, wissen aber in DIESEM Moment noch nicht genau ob wir die User überhaupt brauchen. Beispielsweise möchten wir den Usernamen nur ausgeben, wenn wir mehr als zehn Posts in der Datenbank haben (was in etwa so sinnvoll klingt wie einen Einkaufswagen voll mit Wassermelonen früher im Matheunterricht zu kaufen :)).

Nun benötigen wir ja den Usernamen nur in diesen Fällen, das heißt selbst die zweite Query wäre oft (nämlich wenn wir weniger als zehn Posts haben) schon verschwendete Leistung und damit unnötige Ladezeit. Genau aus diesem Grund gibt es auch noch die Möglichkeit des Lazy Eager Loading.

Das klingt kompliziert, heißt aber ganz einfach, dass wir die zugehörigen User einfach bei Bedarf erst nachladen, nicht automatisch beim Abrufen der Posts. Das sähe dann ungefähr so aus:

Die load() Methode führt in diesem Moment einfach das Eager Loading aus und lädt alle User aus der Datenbank, die zu den Posts in der Collection gehören.

 

Ich hoffe, diese Tipps konnten wieder einige Laravelseiten verschnellern. Wenn du noch weitere Tipps rund um das Thema Performance mit Eloquent hast, teil sie doch bitte in den Kommentaren mit uns.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*