1956 votes

Qu'est-ce que le n+1 selects problème?

Le problème est souvent mentionné en objet-relation cartographie des discussions, et je comprends que cela 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 at-il une plus détaillé--mais simple-explication du problème?

1306voto

Matt Solnit Points 13528

Je ne suis pas un expert, et, le meilleur guide est de Java Persistance avec Hibernate, chapitre 13. Mais je peux essayer de donner un court exemple.

Disons que vous avez une collection d'objets de Voiture (base de données de lignes), et chaque Voiture a une collection de Roue (objets de base de données de lignes). En d'autres termes, Voiture:Roue de 1 à plusieurs relations.

Maintenant, disons que vous avez besoin pour itérer sur toutes les voitures, et pour chacun, d'imprimer une liste de roues. Le naïf O/R de la mise en œuvre permettrait d'effectuer les opérations suivantes:

SELECT * FROM Cars;

/* for each car */
SELECT * FROM Wheel WHERE CarId = ?

En d'autres termes, vous devez sélectionner pour les Voitures, et puis N sélectionne, où N est le nombre total de voitures.

C'est mal :-). Mise en veille prolongée (je ne suis pas familier avec les autres frameworks ORM) vous offre plusieurs façons de le gérer.

124voto

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

Qui vous obtient un ensemble de résultats où les lignes enfants dans table2 cause de la duplication par le retour de la table1 résultats pour chaque enfant en ligne dans la table2. O/R mappeurs doit différencier table1 instances basées sur un champ de clé unique, puis d'utiliser toutes les table2 colonnes à remplir les instances enfant.

SELECT table1.*

SELECT table2.* WHERE SomeFkId = #

Le N+1, où la première requête remplit l'objet primaire et la deuxième requête remplit tous les objets enfants pour chaque de la primaire unique des objets retourné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 à l'adresse "22 de la Vallée de Saint" 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 la Maison avec l'ID=1, Address="22 de la Vallée de Saint" puis de renseigner les Habitants de tableau avec des Gens instances pour Dave, John et Mike avec une seule requête.

Un N+1 de la requête de la même adresse ci-dessus aurait pour résultat:

Id Address
1  22 Valley St

avec une requête distincte, comme

SELECT * FROM Person WHERE HouseId = 1

et résultant dans un autre ensemble de données, comme

Name    HouseId
Dave    1
John    1
Mike    1

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

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

66voto

Summy Points 311

Fournisseur un un-à-plusieurs relation avec le Produit. Un Fournisseur a (fournitures), de nombreux Produits.

***** Table: Supplier *****
+-----+-------------------+
| ID  |       NAME        |
+-----+-------------------+
|  1  |  Supplier Name 1  |
|  2  |  Supplier Name 2  |
|  3  |  Supplier Name 3  |
|  4  |  Supplier Name 4  |
+-----+-------------------+

***** Table: Product *****
+-----+-----------+--------------------+-------+------------+
| ID  |   NAME    |     DESCRIPTION    | PRICE | SUPPLIERID |
+-----+-----------+--------------------+-------+------------+
|1    | Product 1 | Name for Product 1 |  2.0  |     1      |
|2    | Product 2 | Name for Product 2 | 22.0  |     1      |
|3    | Product 3 | Name for Product 3 | 30.0  |     2      |
|4    | Product 4 | Name for Product 4 |  7.0  |     3      |
+-----+-----------+--------------------+-------+------------+

Facteurs:

  • Paresseux mode pour le Fournisseur défini sur "true" (par défaut)

  • Mode de lecture utilisé pour l'interrogation sur le Produit est de Sélectionner

  • Mode de lecture (par défaut): l'information du Fournisseur est accessible

  • La mise en cache ne joue pas un rôle pour la première fois la

  • Le fournisseur est accessible

Mode de lecture est de Sélectionner Fetch (par défaut)

// It takes Select fetch mode as a default
Query query = session.createQuery( "from Product p");
List list = query.list();
// Supplier is being accessed
displayProductsListWithSupplierName(results);

select ... various field names ... from PRODUCT
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?

Résultat:

  • 1 instruction select pour le Produit
  • N sélectionnez les déclarations du Fournisseur

C'est N+1 select problème!

43voto

Mark Goodge Points 21

Je ne peux pas commenter directement sur d'autres réponses, car je n'ai pas assez de réputation. Mais il est intéressant de noter que le problème essentiel se pose parce que, historiquement, beaucoup de sgbd ont été assez pauvre quand il s'agit de la manipulation des jointures (MySQL étant particulièrement remarquable exemple). Donc n+1 a, souvent, été notablement plus rapide qu'une jointure. Et puis il y a des façons d'améliorer n+1, mais encore sans avoir besoin d'une jointure, qui est ce que le problème d'origine est lié.

Toutefois, MySQL est maintenant beaucoup mieux qu'il utilisé pour être quand il s'agit de jointures. Quand j'ai appris MySQL, j'ai utilisé rejoint beaucoup. Puis j'ai découvert la lenteur qu'ils sont, et sont passés à n+1 dans le code à la place. Mais, récemment, j'ai été en train de revenir à joint, parce que MySQL est maintenant un diable de beaucoup mieux à la manipulation d'eux qu'il ne l'était quand j'ai commencé à l'utiliser.

Ces jours-ci, une simple jointure sur une indexé correctement l'ensemble des tables est rarement un problème, en termes de performances. Et si il donne un gain de performance, alors l'utilisation d'indicateurs d'index permet souvent de résoudre.

Cette question est abordée ici par l'un de MySQL équipe de développement:

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 été en évitant les jointures dans le passé à cause de MySQL est insondable de la performance avec eux, puis essayez à nouveau sur les dernières versions. Vous serez probablement agréablement surpris.

29voto

rorycl Points 765

Nous avons déménagé loin de l'ORM de Django à cause de ce problème. En gros, si vous essayez de le faire

for p in person:
    print p.car.colour

L'ORM seront heureux de vous retourner toutes les personnes (généralement dans le cas d'une Personne, d'un objet), mais ensuite, il aura besoin d'interroger la voiture de tableau pour chaque Personne.

Un moyen simple et très efficace, c'est quelque chose que j'appelle "fanfolding", ce qui évite l'absurde idée que les résultats de la requête à partir d'une base de données relationnelle doit être mappé vers les tables d'origine à partir de laquelle la requête est composée.

Étape 1: sélectionnez

  select * from people_car_colour; # this is a view or sql function

Cela renvoie à quelque chose comme

  p.id | p.name | p.telno | car.id | car.type | car.colour
  -----+--------+---------+--------+----------+-----------
  2    | jones  | 2145    | 77     | ford     | red
  2    | jones  | 2145    | 1012   | toyota   | blue
  16   | ashby  | 124     | 99     | bmw      | yellow

Étape 2: Objectiver

Sucer les résultats dans un objet générique créateur avec un argument de split après le troisième élément. Cela signifie que "durand" l'objet ne sera faite plus d'une fois.

Étape 3: Rendre

for p in people:
    print p.car.colour # no more car queries

Voir cette page web pour une mise en œuvre de fanfolding pour python.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X