Le problème N+1 dans Laravel : un arsenal complet de solutions au-delà de l'Eager Loading
Dans l'article précédent, nous avons vu comment l'Eager Loading résout le problème N+1. Mais que faire si vous avez des scénarios complexes ou besoin de performances maximales ? Explorons TOUTES les solutions possibles.
🎯 Méthode 1 : Eager Loading
La solution fondamentale qui couvre 80 % des cas :
// Eager Loading simple
$posts = Post::with('author')->get();
// Chargement de plusieurs relations
$posts = Post::with(['author', 'tags', 'comments'])->get();
// Chargement conditionnel
$posts = Post::with(['author' => function($query) {
$query->select('id', 'name')->where('active', true);
}])->get();
🚀 Méthode 2 : Joins
Lorsque vous n'avez besoin que des données des tables liées :
// Obtenir les posts avec les noms d'auteurs en une seule requête
$posts = Post::join('authors', 'posts.author_id', '=', 'authors.id')
->select('posts.*', 'authors.name as author_name')
->get();
// Utiliser directement dans le template :
@foreach($posts as $post)
<p>Author: {{ $post->author_name }}</p>
@endforeach
⚡ Méthode 3 : Has Many Through
Pour les relations profondément imbriquées :
// Supposons qu'il existe une chaîne : Country -> User -> Post
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(Post::class, User::class);
}
}
// Maintenant, obtenir tous les posts d'un pays avec une seule requête
$country = Country::with('posts')->find(1);
📊 Méthode 4 : Lazy Eager Loading
Lorsque vous ne savez pas à l'avance quelles relations seront nécessaires :
$posts = Post::all();
// Charger les auteurs pour la collection déjà récupérée
$posts->load('author');
// Ou conditionnellement :
if ($someCondition) {
$posts->load('author', 'comments');
}
🎪 Méthode 5 : Agrégats
Lorsque vous n'avez besoin que de comptages ou de sommes :
// AU LIEU DE CECI (N+1 requêtes) :
$posts = Post::all();
foreach($posts as $post) {
echo $post->comments->count(); // Requête séparée pour chaque post
}
// FAITES CECI (2 requêtes) :
$posts = Post::withCount('comments')->get();
foreach($posts as $post) {
echo $post->comments_count; // La valeur est déjà préchargée
}
🔥 Méthode 6 : Raw Queries
Pour des performances maximales dans des cas complexes :
$posts = DB::table('posts')
->leftJoin('authors', 'posts.author_id', '=', 'authors.id')
->leftJoin('comments', 'posts.id', '=', 'comments.post_id')
->select('posts.*', 'authors.name as author_name',
DB::raw('COUNT(comments.id) as comments_count'))
->groupBy('posts.id')
->get();
💾 Méthode 7 : Vues de base de données
Pour les requêtes très complexes utilisées fréquemment :
CREATE VIEW post_details AS
SELECT
p.*,
a.name as author_name,
COUNT(c.id) as comments_count
FROM posts p
LEFT JOIN authors a ON p.author_id = a.id
LEFT JOIN comments c ON p.id = c.post_id
GROUP BY p.id, a.name;
// Utiliser dans Laravel comme une table normale
$posts = DB::table('post_details')->get();
🏗 Méthode 8 : Dénormalisation
Parfois, il est plus simple d'ajouter des données redondantes :
// Ajouter un champ author_name à la table posts
$post = new Post();
$post->title = 'Example';
$post->author_name = $author->name; // Enregistrer une copie
$post->save();
// Maintenant, obtenir le nom de l'auteur sans JOIN :
$posts = Post::all(); // Pas de N+1 !
📝 Méthode 9 : Scopes avec Eager Loading
Créez des scopes réutilisables :
class Post extends Model
{
public function scopeWithRelations($query)
{
return $query->with(['author', 'comments' => function($q) {
$q->latest()->limit(5);
}]);
}
}
// Utilisez partout :
$posts = Post::withRelations()->get();
🎯 Quand utiliser quoi ?
- Eager Loading — pour la plupart des cas
- Joins — lorsque vous n'avez besoin que des données des tables liées
- Has Many Through — pour les relations profondes
- Agrégats — pour les comptages et les sommes
- Raw Queries — pour des performances maximales
- Vues de base de données — pour les requêtes très complexes
- Dénormalisation — pour les données fréquemment demandées
🔍 Comment trouver les zones problématiques ?
// Dans AppServiceProvider
public function boot()
{
if (app()->environment('local')) {
DB::listen(function ($query) {
if (str($query->sql)->contains('where `id` in')) {
Log::warning('Possible N+1 detected: ' . $query->sql);
}
});
}
}
Conclusion : L'Eager Loading est une excellente solution, mais ce n'est pas la seule. Choisissez l'approche en fonction de la tâche spécifique et des exigences de performance.