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

Notions voisines

DébogageMigrationsManyToManyPaginationFormulaireSécurité