[orm] Quel est le problème de requête N + 1 SELECT?


Answers

SELECT 
table1.*
, table2.*
INNER JOIN table2 ON table2.SomeFkId = table1.SomeId

Cela vous obtient un jeu de résultats où les lignes enfant dans table2 provoquent la duplication en retournant les résultats table1 pour chaque ligne enfant dans table2. Les mappeurs O / R doivent différencier les instances table1 en fonction d'un champ clé unique, puis utiliser toutes les colonnes table2 pour remplir les instances enfants.

SELECT table1.*

SELECT table2.* WHERE SomeFkId = #

Le N + 1 est où la première requête remplit l'objet principal et la deuxième requête remplit tous les objets enfants pour chacun des objets principaux uniques renvoyés.

Considérer:

class House
{
    int Id { get; set; }
    string Address { get; set; }
    Person[] Inhabitants { get; set; }
}

class Person
{
    string Name { get; set; }
    int HouseId { get; set; }
}

et des tables avec une structure similaire. Une seule requête pour l'adresse "22 Valley St" peut renvoyer:

Id Address      Name HouseId
1  22 Valley St Dave 1
1  22 Valley St John 1
1  22 Valley St Mike 1

L'O / RM doit remplir une instance de Home avec ID = 1, Address = "22 Valley St", puis remplir le tableau Habitants avec des instances People pour Dave, John et Mike avec une seule requête.

Une requête N + 1 pour la même adresse utilisée ci-dessus entraînerait:

Id Address
1  22 Valley St

avec une requête séparée comme

SELECT * FROM Person WHERE HouseId = 1

et résultant en un ensemble de données distinct comme

Name    HouseId
Dave    1
John    1
Mike    1

et le résultat final étant le même que ci-dessus avec la requête unique.

Les avantages de sélectionner seul est que vous obtenez toutes les données à l'avance, ce qui peut être ce que vous désirez. Les avantages de N + 1 sont la complexité de la requête est réduite et vous pouvez utiliser le chargement paresseux où les ensembles de résultats enfant ne sont chargés qu'à la première demande.

Question

SELECT N + 1 est généralement indiqué comme un problème dans les discussions ORM (Object-Relational Mapping), et je comprends qu'il a quelque chose à faire avec avoir à faire beaucoup de requêtes de base de données pour quelque chose qui semble simple dans le monde objet.

Quelqu'un a-t-il une explication plus détaillée du problème?




Un millionnaire a N voitures. Vous voulez obtenir toutes les (4) roues.

Une (1) requête charge toutes les voitures, mais pour chaque voiture (N), une requête distincte est soumise pour les roues de chargement.

Frais:

Supposons que les index rentrent dans le RAM.

1 + N interrogation analyse et rabotage + recherche d'index ET accès à la plaque 1 + N + (N * 4) pour charger la charge utile.

Supposons que les index ne rentrent pas dans le RAM.

Coûts supplémentaires dans le pire des cas accès à la plaque 1 + N pour l'indice de charge.

Résumé

Le col de la bouteille est accès à la plaque (environ 70 fois par seconde accès aléatoire sur le disque dur) Un joint désireux sélectionnerait également accéder à la plaque 1 + N + (N * 4) fois pour la charge utile. Donc, si les index s'inscrivent dans RAM - pas de problème, c'est assez rapide car seules les opérations de RAM impliqués.




Supposons que vous avez COMPANY et EMPLOYEE. L'ENTREPRISE compte de nombreux EMPLOYÉS (c'est-à-dire que l'EMPLOYÉ a un champ COMPANY_ID).

Dans certaines configurations O / R, lorsque vous avez un objet Company mappé et que vous accédez à ses objets Employee, l'outil O / R effectue une sélection pour chaque employé, alors que si vous étiez en train de faire des opérations SQL, vous pouvez select * from employees where company_id = XX . Donc N (nombre d'employés) plus 1 (compagnie)

C'est ainsi que fonctionnaient les versions initiales d'EJB Entity Beans. Je crois que des choses comme Hibernate ont fait disparaître cela, mais je ne suis pas trop sûr. La plupart des outils incluent généralement des informations sur leur stratégie de cartographie.




Je ne peux pas commenter directement d'autres réponses, car je n'ai pas assez de réputation. Mais il est intéressant de noter que le problème ne se pose essentiellement que parce que, historiquement, beaucoup de dbms ont été très médiocres lorsqu'il s'agit de gérer des jointures (MySQL étant un exemple particulièrement remarquable). Donc, n + 1 a souvent été nettement plus rapide qu'une jointure. Et puis il y a des façons de s'améliorer sur n + 1 mais toujours sans avoir besoin d'une jointure, ce à quoi correspond le problème original.

Cependant, MySQL est maintenant beaucoup mieux que ce qu'il était quand il s'agit de jointures. Quand j'ai appris MySQL pour la première fois, j'ai beaucoup utilisé les jointures. Puis j'ai découvert à quel point ils étaient lents, et j'ai changé à n + 1 dans le code à la place. Mais, récemment, je suis retourné à des jointures, parce que MySQL est maintenant un diable de beaucoup mieux à les manipuler que c'était quand j'ai commencé à l'utiliser.

De nos jours, une simple jointure sur un ensemble de tables correctement indexées est rarement un problème, en termes de performances. Et si cela donne un coup de performance, alors l'utilisation de conseils d'index les résout souvent.

Ceci est discuté ici par l'un des membres de l'équipe de développement de MySQL:

http://jorgenloland.blogspot.co.uk/2013/02/dbt-3-q3-6-x-performance-in-mysql-5610.html

Donc, le résumé est: Si vous avez évité les jointures dans le passé en raison de la performance abyssale de MySQL avec eux, alors essayez à nouveau sur les dernières versions. Vous serez probablement agréablement surpris.




Prenons l'exemple de Matt Solnit, imaginez que vous définissez une association entre Car and Wheels comme LAZY et que vous avez besoin de champs Wheels. Cela signifie qu'après la première sélection, hibernate va faire "Select * from Wheels" où car_id =: id "POUR CHAQUE voiture.

Cela rend le premier choix et plus de 1 sélectionner par chaque voiture N, c'est pourquoi il est appelé problème n + 1.

Pour éviter cela, faites en sorte que l'association soit aussi rapide, afin qu'hibernate charge les données avec une jointure.

Mais attention, si plusieurs fois vous n'accédez pas aux Roues associées, il est préférable de le garder paresseux ou de changer le type de recherche avec les Critères.




À mon avis, l'article écrit dans http://www.realsolve.co.uk/site/tech/hib-tip-pitfall.php?name=why-lazy est exactement le contraire de la vraie question N + 1 est.

Si vous avez besoin d'une explication correcte, veuillez vous reporter à Hibernate - Chapitre 19: Amélioration des performances - Récupération des stratégies

Sélectionner l'extraction (la valeur par défaut) est extrêmement vulnérable à N + 1 sélectionne les problèmes, nous pouvons donc vouloir activer la récupération des jointures




Le problème de sélection N + 1 est une douleur, et il est logique de détecter de tels cas dans les tests unitaires. J'ai développé une petite bibliothèque pour vérifier le nombre de requêtes exécutées par une méthode de test donnée ou juste un bloc arbitraire de code - JDBC Sniffer

Ajoutez simplement une règle JUnit spéciale à votre classe de test et placez l'annotation avec le nombre attendu de requêtes sur vos méthodes de test:

@Rule
public final QueryCounter queryCounter = new QueryCounter();

@Expectation(atMost = 3)
@Test
public void testInvokingDatabase() {
    // your JDBC or JPA code
}



Le lien fourni a un exemple très simple du problème n + 1. Si vous l'appliquez à Hibernate, cela revient à parler de la même chose. Lorsque vous interrogez un objet, l'entité est chargée, mais toutes les associations (sauf configuration contraire) seront chargées paresseusement. D'où une requête pour les objets racine et une autre requête pour charger les associations pour chacun d'eux. 100 objets retournés signifie une requête initiale et ensuite 100 requêtes supplémentaires pour obtenir l'association pour chacun, n + 1.

http://pramatr.com/2009/02/05/sql-n-1-selects-explained/




Related