
Problème N+1
Le problème N+1 survient lorsqu’une requête principale est exécutée pour récupérer une collection d’objets (1 requête), mais qu’ensuite, pour chaque objet de cette collection, une nouvelle requête supplémentaire est lancée pour récupérer ses relations (N requêtes). Au total, tu obtiens N+1 requêtes, ce qui est très inefficace. Voici un exemple
1namespace App\Models; 2 3use Illuminate\Database\Eloquent\Model; 4use Illuminate\Database\Eloquent\Relations\BelongsTo; 5 6class Book extends Model 7{ 8 /** 9 * Récupère l'auteur ayant écrit le livre.10 *11 * @return BelongsTo12 */13 public function author(): BelongsTo14 {15 return $this->belongsTo(Author::class);16 }17}
Maintenant récupérons tous les livres ainsi que leurs auteurs
1use App\Models\Book;2 3$books = Book::all();4 5foreach ($books as $book) {6 echo $book->author->name;7}
Sans optimisation, le code fait 1 requête pour récupérer la liste des livres, puis 1 requête supplémentaire pour chaque livre afin de trouver son auteur. Donc, avec 25 livres, on obtient 26 requêtes au total. Résultat des requêtes :
1-- On séléctionne les livres2SELECT * FROM books;3 4-- Pour chaque passage dans la bloucle, on séléctionne chaque auteur du livre5SELECT * FROM authors WHERE authors.id = 1; -- pour le premier livre6SELECT * FROM authors WHERE authors.id = 2; -- pour le deuxième livre7SELECT * FROM authors WHERE authors.id = 3; -- pour le troisième livre8-- jusqu'à totaliser 25 requêtes
Qu’est-ce que l’Eager Loading ?
L’eager loading est une technique qui permet de charger les relations d’un modèle en avance, en même temps que la requête principale. Cela évite le problème classique N+1, où chaque élément de la collection déclenche une nouvelle requête pour charger ses relations.
Comment ça marche ?
Pour notre exemple, on peut utilises le préchargement (eager loading) pour réduire l'opération à seulement deux requêtes. Lors de la construction d’une requête, vous pouvez préciser quelles relations doivent être chargées de façon anticipée en utilisant la méthode with
. Notre code deviens alors :
1$books = Book::with('author')->get();2 3foreach ($books as $book) {4 echo $book->author->name;5}
Pour cette opération, seulement deux requêtes seront exécutées : une pour récupérer tous les livres, et une pour récupérer tous les auteurs de ces livres. Résultat des requêtes :
1-- On séléctionne les livres2SELECT * FROM books;3 4-- On séléctionne tous les auteurs des livres5-- séléctionnés en une seule requête et Laravel6-- se chargera de les associés aux livres7SELECT * FROM authors WHERE authors.id IN (1, 2, 3,..., 25);
Précharger plusieurs rélations
On peut précharger plusieurs rélations en même temps. Dans ce cas, au lieu de passer le nom de la rélation en paramètre dans la méthode with
, on passe plutôt un tableau
1$books = Book::with(['author', 'publisher'])->get();
Préchargement imbriqué
Pour précharger les relations d’une relation, vous pouvez utiliser la syntaxe par “points” (dot syntax) ou un tableu imbriqué. Par exemple, chargons de façon anticipée tous les auteurs d’un livre ainsi que tous les contacts personnels de chaque auteur
1// Synthax “points” (dot syntax) 2$books = Book::with('author.contacts')->get(); 3 4// Tableau imbriqué 5$books = Book::with([ 6 'author' => [ 7 'contacts', 8 'publisher', 9 ],10])->get();
Chargement dynamique (Lazy Eager Loading)
Parfois, il peut être nécessaire de charger de manière anticipée une relation après que le modèle parent ait déjà été récupéré. Par exemple, cela peut être utile si vous devez décider dynamiquement de charger ou non les modèles associés :
1use App\Models\Book;2 3$books = Book::all();4 5if ($condition) {6 $books->load('author', 'publisher');7}
Astuces et conseils
Préchargement automatique
⚠️ Cette fonctionnalité est actuellement en version bêta afin de recueillir les retours de la communauté. Son comportement et ses fonctionnalités peuvent évoluer, même lors de mises à jour mineures.
Dans de nombreux cas, Laravel peut précharger automatiquement les relations auxquelles vous accédez. Pour activer ce chargement automatique, vous devez appeler la méthode Model::automaticallyEagerLoadRelationships
dans la méthode boot
du AppServiceProvider
de votre application
1use Illuminate\Database\Eloquent\Model;2 3/**4 * Bootstrap any application services.5 */6public function boot(): void7{8 Model::automaticallyEagerLoadRelationships();9}
Si vous ne souhaitez pas activer le préchargement automatique de manière globale, vous pouvez tout de même activer cette fonctionnalité pour une seule instance de collection Eloquent en appelant la méthode withRelationshipAutoloading
sur cette collection
1$books = Book::where('published', true)->get();2 3$books->withRelationshipAutoloading();
Prévention du Lazy Loading
Comme nous l’avons vu précédemment, le chargement anticipé (eager loading) des relations peut souvent améliorer considérablement les performances de votre application. Par conséquent, si vous le souhaitez, vous pouvez demander à Laravel d’empêcher systématiquement le chargement paresseux (lazy loading) des relations. Pour ce faire, vous pouvez appeler la méthode preventLazyLoading
fournie par la classe de base des modèles Eloquent. En général, cette méthode est appelée dans la méthode boot
de la classe AppServiceProvider
de votre application.
1use Illuminate\Database\Eloquent\Model;2 3/**4 * Bootstrap any application services.5 */6public function boot(): void7{8 Model::preventLazyLoading(! $this->app->isProduction());9}
Optimisation
Quand vous préchargez les relation, Laravel va sélectionner tous les champs de la table démandée, ce qui n'est pas très optimisé quand on veut seulement utilisé quelques champs, par exemple :
1@php 2 $books = Book::with('author')->get(); 3@endphp 4 5@foreach($books as $book) 6 <div> 7 <h4>{{ $book->title }}</h4> 8 <div> 9 <p>{{ $book->published_at->diffForHumans() }}<p>10 <p>{{ $book->author->full_name }}</p>11 </div>12 </div>13@endforeach
Dans cet exemple, on a préchargé les auteurs des livres pour afficher seulement leurs noms dans la boucle. Mais dans la requête on a séléctionné tous les champs dans la table. Ce qui n'est pas très optimisé. La solution est de préciser les champs qu'on veut via la méthode with
.
1@php 2 $books = Book::with('author:id,full_name')->get(); 3@endphp 4 5@foreach($books as $book) 6 <div> 7 <h4>{{ $book->title }}</h4> 8 <div> 9 <p>{{ $book->published_at->diffForHumans() }}<p>10 <p>{{ $book->author->full_name }}</p>11 </div>12 </div>13@endforeach
On doit préciser l'id pour que Laravel l'utilise pour associer le model à la rélation
En savoir plus
Pour approfondir le sujet et consulter la documentation officielle, voici quelques ressources utiles :
- Documentation Laravel Eloquent – Guide complet sur l’ORM Eloquent.
- Eager Loading dans Laravel – Explications et exemples détaillés sur le préchargement des relations (Eager Loading).