salut! Je suis Valerio, un développeur italien et CTO de la plateforme
Inspector.dev
.
Dans cet article, je vais partager un ensemble de stratégies d'optimisation ORM que j'utilise lors du développement de services backend.
Je suis sûr que chacun de nous a dû se plaindre que le serveur ou l'application fonctionnait lentement (ou même ne fonctionnait pas du tout), et passer le temps à la machine à café, en attendant les résultats d'une longue demande.
Comment le réparer?
Découvrons-le!
La base de données est une ressource partagée
Pourquoi la base de données pose-t-elle autant de problèmes de performances?
On oublie souvent qu'aucune requête n'est indépendante des autres.
Nous pensons que même si certaines requêtes sont lentes, elles n'affectent guère les autres ... Mais est-ce vraiment le cas?
Une base de données est une ressource partagée utilisée par tous les processus qui s'exécutent dans votre application. Même une méthode d'accès à la base de données mal conçue peut perturber les performances de l'ensemble du système.
Par conséquent, n'oubliez pas les conséquences possibles en pensant: "C'est normal que ce morceau de code ne soit pas optimisé!" Un accès lent à la base de données peut entraîner sa surcharge, ce qui, à son tour, peut affecter négativement l'expérience utilisateur.
Problème de requête de base de données N + 1
Quel est le problème N + 1?
Il s'agit d'un problème courant lors de l'utilisation d'un ORM pour interagir avec une base de données. Il ne s'agit pas d'écrire du code SQL.
Lorsque vous utilisez un système ORM comme Eloquent, il n'est pas toujours évident de savoir quelles requêtes seront exécutées et quand. Dans le contexte de ce problème particulier, parlons des relations et du chargement impatient.
Tout système ORM vous permet de déclarer les relations entre les entités et fournit une excellente API pour naviguer dans la structure de votre base de données.
Vous trouverez ci-dessous un bon exemple pour les entités «Article» et «Auteur».
/*
* Each Article belongs to an Author
*/
$article = Article::find("1");
echo $article->author->name;
/*
* Each Author has many Articles
*/
foreach (Article::all() as $article)
{
echo $article->title;
}
Cependant, lorsque vous utilisez des relations dans une boucle, vous devez écrire votre code avec soin.
Jetez un œil à l'exemple ci-dessous.
Nous voulons ajouter le nom de l'auteur à côté du titre de l'article. Avec l'ORM, vous pouvez obtenir le nom de l'auteur en utilisant une relation un à un entre l'article et l'auteur.
Tout semble simple:
// Initial query to grab all articles
$articles = Article::all();
foreach ($articles as $article)
{
// Get the author to print the name.
echo $article->title . ' by ' . $article->author->name;
}
Mais ensuite nous sommes tombés dans un piège!
Cette boucle génère une requête initiale pour obtenir tous les articles:
SELECT * FROM articles;
et N requêtes supplémentaires pour obtenir l'auteur de chaque article et afficher la valeur du champ "nom", même si l'auteur est toujours le même.
SELECT * FROM author WHERE id = [articles.author_id]
Nous recevons exactement N + 1 demandes.
Cela peut ne pas sembler un gros problème. Eh bien, faisons quinze ou vingt demandes supplémentaires - ce n'est pas grave. Cependant, revenons à la première partie de cet article:
- — , .
- , , .
- , .
:
Selon la documentation Laravel, il y a de fortes chances que vous rencontriez le problème de requête N + 1, car lorsque vous accédez aux relations Eloquent en tant que propriétés (
$article->author
), les données de relation sont chargées paresseusement.
Cela signifie que les données de relation ne sont pas chargées jusqu'à ce que vous accédiez pour la première fois à la propriété.
Cependant, en utilisant une méthode simple, nous pouvons charger toutes les données de relation à la fois. Ensuite, lors de l'accès à la relation Eloquent en tant que propriété, le système ORM n'exécutera pas de nouvelle requête car les données ont déjà été chargées.
Cette tactique est appelée "chargement hâtif" et est prise en charge par tous les ORM.
// Eager load authors using "with".
$articles = Article::with('author')->get();
foreach ($articles as $article)
{
// Author will not run a query on each iteration.
echo $article->author->name;
}
Eloquent fournit une méthode
with()
pour charger rapidement les relations.
Dans ce cas, seules deux requêtes seront exécutées.
Le premier est nécessaire pour télécharger tous les articles:
SELECT * FROM articles;
Le second sera exécuté par la méthode
with()
et récupérera tous les auteurs:
SELECT * FROM authors WHERE id IN (1, 2, 3, 4, ...);
Le mécanisme interne d'Eloquent cartographiera les données et il sera accessible de la manière habituelle:
$article->author->name;
Optimisez vos opérateurs select
Pendant longtemps, j'ai pensé que déclarer explicitement le nombre de champs dans une requête d'extraction ne conduisait pas à une amélioration significative des performances, donc pour plus de simplicité, j'ai obtenu tous les champs dans mes requêtes.
En outre, la spécification rigide de la liste des champs dans une instruction de sélection spécifique rend plus difficile la maintenance d'un tel morceau de code.
Le plus gros écueil de cet argument est que du point de vue de la base de données, cela peut effectivement être vrai.
Cependant, nous travaillons avec un ORM, donc les données sélectionnées dans la base de données seront chargées en mémoire côté PHP afin que le système ORM les gère davantage. Plus nous capturons de champs, plus le processus prendra de mémoire.
Laravel Eloquent fournit une méthode de sélection pour restreindre la requête aux seules colonnes dont nous avons besoin:
$articles = Article::query()
->select('id', 'title', 'content') // The fields you need
->latest()
->get();
En excluant des champs, l'interpréteur PHP n'a pas à traiter les données inutiles, vous pouvez donc réduire considérablement la consommation de mémoire.
Le fait d'éviter l'extraction complète peut également améliorer les performances de tri, de regroupement et de fusion, car la base de données elle-même peut ainsi économiser de la mémoire.
Utiliser des vues dans MySQL
Les vues sont des requêtes SELECT basées sur d'autres tables et stockées dans la base de données.
Lorsque nous SELECT une ou plusieurs tables, la base de données compile d'abord notre instruction SQL, s'assure qu'elle est exempte d'erreurs, puis récupère les données.
Une vue est une instruction SELECT précompilée qui, lorsqu'elle est traitée, MySQL exécute immédiatement la requête interne sous-jacente de la vue.
De plus, MySQL est généralement plus intelligent que PHP en matière de filtrage des données. Il y a des gains de performances significatifs lors de l'utilisation des vues par rapport à l'utilisation des fonctions PHP pour traiter des collections ou des tableaux.
Si vous souhaitez en savoir plus sur les capacités de MySQL pour développer des applications gourmandes en bases de données, consultez ce site formidable: www.mysqltutorial.org
Lier un modèle éloquent à une vue
Les vues sont également appelées «tables virtuelles». Du point de vue de l'ORM, ils ressemblent à des tables régulières.
Par conséquent, vous pouvez créer un modèle Eloquent pour interroger les données qui se trouvent dans la vue.
class ArticleStats extends Model
{
/**
* The name of the view is the table name.
*/
protected $table = "article_stats_view";
/**
* If the resultset of the View include the "author_id"
* we can use it to retrieve the author as normal relation.
*/
public function author()
{
return $this->belongsTo(Author::class);
}
}
Les relations fonctionnent comme d'habitude, tout comme les coercitions, la pagination, etc. Et il n'y a pas de pénalité de performance.
Conclusion
J'espère que ces conseils vous aideront à développer des logiciels plus fiables et évolutifs.
Tous les exemples de code sont écrits en utilisant Eloquent comme ORM, mais gardez à l'esprit que ces stratégies fonctionnent de la même manière pour tous les ORM majeurs.
Comme je le dis souvent, nous avons besoin d'outils pour mettre en œuvre des stratégies efficaces. Et s'il n'y a pas de stratégie, alors il n'y a rien à dire.
Merci beaucoup d'avoir lu l'article jusqu'au bout. Si vous souhaitez en savoir plus sur Inspector, je vous invite sur notre site Internet www.inspector.dev . N'hésitez pas à écrire au chat si vous avez des questions!
Précédemment posté ici: www.inspector.dev/make-your-application-scalable-optimizing-the-orm-performance
Lire la suite: