2259 Stimmen

Was ist das "N+1 Selects-Problem" bei ORM (Object-Relational Mapping)?

Das "N+1-Selects-Problem" wird im Allgemeinen als Problem in Diskussionen über objektrelationale Zuordnungen (ORM) genannt, und ich verstehe, dass es etwas damit zu tun hat, dass man viele Datenbankabfragen für etwas machen muss, das in der Objektwelt einfach erscheint.

Hat jemand eine genauere Erklärung für dieses Problem?

2 Stimmen

Es gibt einige hilfreiche Beiträge, die sich mit diesem Problem und der möglichen Lösung befassen. Häufige Anwendungsprobleme und deren Behebung: Das Select N + 1 Problem , Die (silberne) Kugel für das N+1 Problem , Langsames Laden - eifriges Laden

0 Stimmen

Für alle, die nach einer Lösung für dieses Problem suchen, habe ich einen Beitrag gefunden, der es beschreibt. stackoverflow.com/questions/32453989/

54 Stimmen

Wenn man die Antworten bedenkt, sollte man dies nicht als 1+N Problem bezeichnen? Da es sich um eine Terminologie zu handeln scheint, frage ich nicht speziell OP.

11voto

Der angegebene Link enthält ein sehr einfaches Beispiel für das n + 1 Problem. Wenn man es auf Hibernate anwendet, geht es im Grunde um die gleiche Sache. Wenn Sie ein Objekt abfragen, wird die Entität geladen, aber alle Assoziationen (sofern nicht anders konfiguriert) werden verzögert geladen. Daraus ergibt sich eine Abfrage für die Root-Objekte und eine weitere Abfrage zum Laden der Assoziationen für jedes dieser Objekte. 100 zurückgegebene Objekte bedeuten eine erste Abfrage und dann 100 zusätzliche Abfragen, um die Assoziationen für jedes n + 1 zu erhalten.

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

10voto

bedrin Punkte 4303

N+1 select Problem ist ein Schmerz, und es macht Sinn, solche Fälle in Unit-Tests zu erkennen. Ich habe eine kleine Bibliothek entwickelt, um die Anzahl der Abfragen zu überprüfen, die von einer bestimmten Testmethode oder einem beliebigen Codeblock ausgeführt werden - JDBC-Schnüffler

Fügen Sie einfach eine spezielle JUnit-Regel zu Ihrer Testklasse hinzu und platzieren Sie eine Annotation mit der erwarteten Anzahl von Abfragen auf Ihren Testmethoden:

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

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

9voto

Jimmy Punkte 686

N+1 Problem in Hibernate & Spring Data JPA

Das N+1-Problem ist ein Leistungsproblem beim objektrelationalen Mapping, bei dem für eine einzige Select-Abfrage in der Anwendungsschicht mehrere Select-Abfragen (N+1 um genau zu sein, wobei N = Anzahl der Datensätze in der Tabelle) in der Datenbank ausgelöst werden. Hibernate und Spring Data JPA bieten mehrere Möglichkeiten, dieses Leistungsproblem abzufangen und zu lösen.

Was ist das N+1 Problem?

Um das N+1 Problem zu verstehen, betrachten wir ein Szenario. Nehmen wir an, wir haben eine Sammlung von Benutzer Objekte, die auf DB_USER Tabelle in der Datenbank, und jeder Benutzer hat eine Sammlung oder Rolle abgebildet auf DB_ROLE Tabelle über eine Verbindungstabelle DB_USER_ROLE . Auf der ORM-Ebene wird eine Benutzer hat viele an viele Beziehung zu Rolle .

Entity Model
@Entity
@Table(name = "DB_USER")
public class User {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private String name;

    @ManyToMany(fetch = FetchType.LAZY)                   
    private Set<Role> roles;
    //Getter and Setters 
 }

@Entity
@Table(name = "DB_ROLE")
public class Role {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Long id;

    private String name;
    //Getter and Setters
 }

Ein Benutzer kann mehrere Rollen haben. Rollen werden träge geladen. Nehmen wir nun an, wir wollen alle Benutzer aus dieser Tabelle abrufen und die Rollen der einzelnen Benutzer ausdrucken . Eine sehr naive objektrelationale Implementierung könnte - UserRepository con findAllBy Methode

public interface UserRepository extends CrudRepository<User, Long> {

    List<User> findAllBy();
}

Die entsprechenden SQL-Abfragen, die von ORM ausgeführt werden, lauten:

Erst bekommen Alle Benutzer (1)

Select * from DB_USER;

Dann erhalten Rollen für jeden Benutzer N-mal ausgeführt (wobei N die Anzahl der Benutzer ist)

Select * from DB_USER_ROLE where userid = <userid>;

Wir brauchen also eine Auswahl für Benutzer y N zusätzliche Selects zum Abrufen von Rollen für jeden Benutzer , wobei N ist die Gesamtzahl der Nutzer . Dies ist ein klassisches N+1-Problem im ORM .

Wie kann man sie identifizieren?

Hibernate bietet eine Tracing-Option, die die SQL-Protokollierung in der Konsole/den Protokollen ermöglicht. Anhand der Protokolle können Sie leicht feststellen, ob Hibernate für einen bestimmten Aufruf N+1 Abfragen ausführt .

Wenn Sie mehrere SQL-Einträge für eine bestimmte Select-Abfrage sehen, ist die Wahrscheinlichkeit groß, dass es sich um ein N+1-Problem handelt.

N+1 Auflösung

Auf SQL-Ebene Was ORM erreichen muss, um N+1 zu vermeiden, ist, dass eine Abfrage auslösen, die die beiden Tabellen verbindet und die kombinierten Ergebnisse in einer einzigen Abfrage erhalten .

Fetch Join SQL, das alles (Benutzer und Rollen) in einer einzigen Abfrage abruft

OR Einfaches SQL

select user0_.id, role2_.id, user0_.name, role2_.name, roles1_.user_id, roles1_.roles_id from db_user user0_ left outer join db_user_roles roles1_ on user0_.id=roles1_.user_id left outer join db_role role2_ on roles1_.roles_id=role2_.id

Hibernate und Spring Data JPA bieten Mechanismen zur Lösung des N+1 ORM-Problems.

1. Spring Data JPA-Ansatz:

Wenn wir Spring Data JPA verwenden, haben wir zwei Möglichkeiten, dies zu erreichen - mit EntityGraph oder mit select-Abfrage mit fetch join.

public interface UserRepository extends CrudRepository<User, Long> {

    List<User> findAllBy();             

    @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles")  
    List<User> findWithoutNPlusOne();

    @EntityGraph(attributePaths = {"roles"})                
    List<User> findAll();
}

N+1 Abfragen werden auf Datenbankebene mit Left Join Fetch durchgeführt, wir lösen das N+1 Problem mit AttributePaths, Spring Data JPA vermeidet das N+1 Problem

2. Hibernate-Ansatz:

Wenn es sich um einen reinen Hibernate handelt, dann funktionieren die folgenden Lösungen.

Verwendung von HQL :

from User u *join fetch* u.roles roles roles

Verwendung von Kriterien API:

Criteria criteria = session.createCriteria(User.class);
criteria.setFetchMode("roles", FetchMode.EAGER);

Alle diese Ansätze funktionieren ähnlich und sie stellen eine ähnliche Datenbankabfrage mit Left Join Fetch

7voto

Adam Gent Punkte 45977

Das Problem, wie andere mehr elegant erklärt haben, ist, dass Sie entweder ein kartesisches Produkt der OneToMany-Spalten haben oder Sie tun N+1 Selects. Entweder möglich gigantische Resultset oder chatty mit der Datenbank bzw..

Ich bin überrascht, dass dies nicht erwähnt wird, aber so habe ich dieses Problem umgangen... Ich mache eine halb-temporäre ID-Tabelle . Ich mache das auch, wenn Sie die IN () Einschränkung der Klausel .

Dies funktioniert nicht in allen Fällen (wahrscheinlich nicht einmal in der Mehrheit), aber es funktioniert besonders gut, wenn Sie viele untergeordnete Objekte haben, so dass das kartesische Produkt aus dem Ruder läuft (d.h. viele OneToMany Spalten ist die Anzahl der Ergebnisse eine Multiplikation der Spalten) und es handelt sich eher um eine stapelartige Aufgabe.

Zunächst fügen Sie die IDs Ihrer übergeordneten Objekte als Stapel in eine ID-Tabelle ein. Diese batch_id ist etwas, das wir in unserer App generieren und aufbewahren.

INSERT INTO temp_ids 
    (product_id, batch_id)
    (SELECT p.product_id, ? 
    FROM product p ORDER BY p.product_id
    LIMIT ? OFFSET ?);

Jetzt für jede OneToMany Spalte machen Sie einfach eine SELECT in der Tabelle ids INNER JOIN der untergeordneten Tabelle mit einer WHERE batch_id= (oder andersherum). Sie sollten nur sicherstellen, dass Sie nach der id-Spalte ordnen, da dies das Zusammenführen von Ergebnisspalten erleichtert (andernfalls benötigen Sie eine HashMap/Tabelle für die gesamte Ergebnismenge, was vielleicht nicht so schlecht ist).

Dann bereinigen Sie einfach regelmäßig die ID-Tabelle.

Dies funktioniert auch besonders gut, wenn der Benutzer z.B. 100 oder mehr verschiedene Artikel für eine Art Massenverarbeitung auswählt. Legen Sie die 100 eindeutigen IDs in der temporären Tabelle ab.

Die Anzahl der Abfragen, die Sie jetzt durchführen, entspricht der Anzahl der OneToMany-Spalten.

5voto

Toma Velev Punkte 53

Ohne auf die Details der Tech-Stack-Implementierung einzugehen, gibt es architektonisch gesehen mindestens zwei Lösungen für das N + 1 Problem:

  • Haben nur 1 - große Abfrage - mit Joins. Dies führt dazu, dass viele Informationen von der Datenbank zur Anwendungsschicht transportiert werden müssen, insbesondere wenn es mehrere untergeordnete Datensätze gibt. Das typische Ergebnis einer Datenbank ist eine Reihe von Zeilen, nicht ein Graph von Objekten (es gibt Lösungen für dieses Problem mit verschiedenen DB-Systemen)
  • Haben zwei (oder mehr für mehr Kinder benötigt, um verbunden werden) Abfragen - 1 für die Eltern und nachdem Sie sie haben - Abfrage von IDs die Kinder und ordnen Sie sie. Dies minimiert den Datentransfer zwischen der DB und den APP-Ebenen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X