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.

3voto

Lukas Eder Punkte 194234

Eine Verallgemeinerung von N+1

Das N+1-Problem ist ein ORM-spezifischer Name für ein Problem, bei dem man Schleifen, die sinnvollerweise auf einem Server ausgeführt werden könnten, auf den Client verschiebt. Das generische Problem ist nicht spezifisch für ORMs, es kann bei jeder Remote-API auftreten. In diesem Artikel habe ich gezeigt, dass JDBC-Roundtrips sehr kostspielig sind wenn Sie eine API N-mal statt nur 1-mal aufrufen. Der Unterschied in diesem Beispiel besteht darin, ob Sie die Oracle PL/SQL-Prozedur aufrufen:

  • dbms_output.get_lines (einmaliger Aufruf, Erhalt von N Elementen)
  • dbms_output.get_line (N-mal aufrufen, jedes Mal 1 Element erhalten)

Sie sind logisch äquivalent, aber aufgrund der Latenzzeit zwischen Server und Client fügen Sie N Latenz-Wartezeiten zu Ihrer Schleife hinzu, anstatt nur einmal zu warten.

Der ORM-Fall

Tatsächlich ist das ORM-y N+1-Problem nicht einmal ORM-spezifisch, Sie können es auch erreichen, indem Sie Ihre eigenen Abfragen manuell ausführen, z. B. wenn Sie so etwas in PL/SQL tun:

-- This loop is executed once
for parent in (select * from parent) loop

  -- This loop is executed N times
  for child in (select * from child where parent_id = parent.id) loop
    ...
  end loop;
end loop;

Es wäre viel besser, dies über eine Verknüpfung (in diesem Fall) zu realisieren:

for rec in (
  select *
  from parent p
  join child c on c.parent_id = p.id
)
loop
  ...
end loop;

Jetzt wird die Schleife nur noch einmal ausgeführt, und die Logik der Schleife wurde vom Client (PL/SQL) auf den Server (SQL) verlagert, der sie sogar noch anders optimieren kann, z. B. durch einen Hash Join ( O(N) ) als eine verschachtelte Schleifenverbindung ( O(N log N) mit Index)

Automatische Erkennung von N+1-Problemen

Wenn Sie JDBC verwenden, Sie könnten jOOQ als JDBC-Proxy hinter den Kulissen verwenden, um Ihre N+1-Probleme automatisch zu erkennen. Der jOOQ-Parser normalisiert Ihre SQL-Abfragen und speichert Daten über aufeinanderfolgende Ausführungen von Eltern- und Kind-Abfragen. Dies funktioniert sogar, wenn Ihre Abfragen nicht genau gleich, aber semantisch äquivalent sind.

2voto

martins.tuga Punkte 1584

Nehmen wir das Beispiel von Matt Solnit: Stellen Sie sich vor, Sie definieren eine Assoziation zwischen Auto und Rädern als LAZY und Sie benötigen einige Felder für Räder. Das bedeutet, dass Hibernate nach der ersten Auswahl "Select * from Wheels where car_id = :id" FÜR JEDES Auto durchführen wird.

Dies macht die erste Auswahl und weitere 1 Auswahl für jedes N Auto, deshalb wird es n+1 Problem genannt.

Um dies zu vermeiden, stellen Sie die Assoziation fetch als eager ein, so dass Hibernate Daten mit einer Verknüpfung lädt.

Aber Achtung, wenn Sie oft nicht auf die zugehörigen Wheels zugreifen, ist es besser, sie LAZY zu halten oder den Abruftyp mit Criteria zu ändern.

1voto

Adam Gaj Punkte 155

Das N+1 SELECT-Problem ist wirklich schwer zu erkennen, insbesondere bei Projekten mit großen Bereichen, bis zu dem Zeitpunkt, an dem es beginnt, die Leistung zu beeinträchtigen. Selbst wenn das Problem behoben ist, z. B. durch Hinzufügen von Eager Loading, kann eine weitere Entwicklung die Lösung zerstören und/oder das N+1 SELECT-Problem an anderen Stellen wieder einführen.

Ich habe eine Open-Source-Bibliothek erstellt jplusone um diese Probleme in JPA-basierten Spring Boot Java-Anwendungen zu lösen. Die Bibliothek bietet zwei Hauptfunktionen:

  1. Erzeugt Berichte, die SQL-Anweisungen mit den Ausführungen von JPA-Operationen korrelieren, die sie ausgelöst haben, und die Stellen im Quellcode Ihrer Anwendung, die daran beteiligt waren

    2020-10-22 18:41:43.236 DEBUG 14913 --- [ main] c.a.j.core.report.ReportGenerator : ROOT com.adgadev.jplusone.test.domain.bookshop.BookshopControllerTest.shouldGetBookDetailsLazily(BookshopControllerTest.java:65) com.adgadev.jplusone.test.domain.bookshop.BookshopController.getSampleBookUsingLazyLoading(BookshopController.java:31) com.adgadev.jplusone.test.domain.bookshop.BookshopService.getSampleBookDetailsUsingLazyLoading [PROXY] SESSION BOUNDARY OPERATION [IMPLICIT] com.adgadev.jplusone.test.domain.bookshop.BookshopService.getSampleBookDetailsUsingLazyLoading(BookshopService.java:35) com.adgadev.jplusone.test.domain.bookshop.Author.getName [PROXY] com.adgadev.jplusone.test.domain.bookshop.Author [FETCHING ENTITY] STATEMENT [READ] select [...] from author author0_ left outer join genre genre1_ on author0_.genre_id=genre1_.id where author0_.id=1 OPERATION [IMPLICIT] com.adgadev.jplusone.test.domain.bookshop.BookshopService.getSampleBookDetailsUsingLazyLoading(BookshopService.java:36) com.adgadev.jplusone.test.domain.bookshop.Author.countWrittenBooks(Author.java:53) com.adgadev.jplusone.test.domain.bookshop.Author.books [FETCHING COLLECTION] STATEMENT [READ] select [...] from book books0_ where books0_.author_id=1

  2. Bietet eine API, die es ermöglicht, Tests zu schreiben, mit denen überprüft werden kann, wie effektiv Ihre Anwendung JPA nutzt (z. B. Feststellung der Anzahl der "Lazy Loading"-Vorgänge)

    @SpringBootTest class LazyLoadingTest {

    @Autowired
    private JPlusOneAssertionContext assertionContext;
    
    @Autowired
    private SampleService sampleService;
    
    @Test
    public void shouldBusinessCheckOperationAgainstJPlusOneAssertionRule() {
        JPlusOneAssertionRule rule = JPlusOneAssertionRule
                .within().lastSession()
                .shouldBe().noImplicitOperations().exceptAnyOf(exclusions -> exclusions
                        .loadingEntity(Author.class).times(atMost(2))
                        .loadingCollection(Author.class, "books")
                );
    
        // trigger business operation which you wish to be asserted against the rule,
        // i.e. calling a service or sending request to your API controller
        sampleService.executeBusinessOperation();
    
        rule.check(assertionContext);
    }

    }

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