Requêtes N+1 avec Doctrine : les repérer et les éviter
Les requêtes N+1 avec Doctrine : une requête pour la liste, puis une de plus par élément dès qu'une boucle parcourt une relation chargée à la demande.
// Dans CategorieRepository : la carte entière, plats et ingrédients compris, en une requête. return $this->createQueryBuilder('c') ->leftJoin('c.plats', 'p') ->addSelect('p') ->leftJoin('p.ingredients', 'i') ->addSelect('i') ->orderBy('c.position', 'ASC') ->getQuery() ->getResult();
Doctrine charge les relations paresseusement : tant qu'on ne parcourt pas $categorie->getPlats(), rien ne part en base ; au premier parcours, une requête part, et une boucle en envoie autant qu'elle a d'éléments. Rien ne casse, la page reste juste, elle ralentit seulement à mesure que les données grossissent. La parade habituelle est la jointure ci-dessus, leftJoin() suivi de addSelect(), qui ramène la liste et ses relations d'un coup. Le piège : une jointure sans addSelect() filtre mais n'hydrate rien, et les requêtes reviennent ; un innerJoin() fait disparaître les éléments qui n'ont pas de relation.
Dans la taverne, un compteur de requêtes SQL en bas de chaque page sert de témoin : les exercices font charger toute la carte en une requête, retrouver une régression qui ne lève aucune erreur, et tenir un budget de requêtes sur une page neuve.
3 exercices font travailler cette notion, en écrivant du code dans le navigateur.
Dans les parcours
- Le coup de feu du vendredi Symfony pour les devs PHP · chapitre 8 : Retour à la cave
- 🐉 Boss : le cellier effondré Symfony pour les devs PHP · chapitre 8 : Retour à la cave
- L'affiche des soirées Symfony pour les devs PHP · chapitre 12 : Le grand service