FetchMode Join vs SubSelect

7

Ich habe zwei Tabellen Employee und Department folgen die Entity-Klassen für beide

%Vor%

Im Folgenden werden die Abfragen ausgelöst, wenn ich em.find(Department.class, 1);

ausgeführt habe

- Abrufmodus = fetchmode.join

%Vor%

- Abrufmodus = fetchmode_select

%Vor%

Ich wollte nur wissen, welche wir bevorzugen FetchMode.JOIN oder FetchMode.SUBSELECT ? Welches sollten wir in welchem ​​Szenario wählen?

    
eatSleepCode 07.10.2015, 05:59
quelle

4 Antworten

20

Die SUBQUERY-Strategie, auf die Marmite sich bezieht, bezieht sich auf FetchMode.SELECT, nicht auf SUBSELECT.

Die Konsolenausgabe, die Sie über fetchmode_select gepostet haben, ist seltsam, weil das nicht so funktioniert.

Die FetchMode.SUBSELECT

  

Verwenden Sie eine Subselect-Abfrage, um die zusätzlichen Sammlungen zu laden

Ruhezustand Dokumentation :

  

Wenn eine faule Sammlung oder ein einwertige Proxy abgerufen werden muss, lädt Hibernate alle von ihnen und führt die ursprüngliche Abfrage in einem Subselect erneut aus. Dies funktioniert auf die gleiche Weise wie beim Stapeln, aber ohne das stückweise Laden.

FetchMode.SUBSELECT sollte etwa so aussehen:

%Vor%

Sie können sehen, dass diese zweite Abfrage die all Mitarbeiter, die zu einer Abteilung gehören (dh employee.department_id ist nicht null), in den Speicher bringt, es spielt keine Rolle, ob es die Abteilung ist dass Sie in Ihrer ersten Abfrage abrufen. Dies ist möglicherweise ein großes Problem, wenn die Tabelle der Mitarbeiter groß ist, weil es möglicherweise versehentlich eine ganze Datenbank lädt in den Speicher .

Allerdings verringert FetchMode.SUBSELECT die Anzahl der Abfragen erheblich, da nur zwei Abfragen im Vergleich zu den N + 1-Abfragen von FecthMode.SELECT erforderlich sind.

Sie denken vielleicht, dass FetchMode.JOIN noch weniger Abfragen macht, nur 1, warum also SUBSELECT überhaupt verwenden? Nun, es ist wahr, aber auf Kosten doppelter Daten und einer stärkeren Antwort.

Wenn ein einwertige Proxy mit JOIN abgerufen werden muss, kann die Abfrage Folgendes abrufen:

%Vor%

Die Mitarbeiterdaten des Chefs werden dupliziert, wenn er mehr als eine Abteilung leitet und die Kosten in der Bandbreite liegen.

Wenn eine faule Auflistung mit JOIN abgerufen werden muss, ruft die Abfrage möglicherweise Folgendes ab:

%Vor%

Die Daten der Abteilung werden dupliziert, wenn sie mehr als einen Mitarbeiter enthält (der natürliche Fall). Wir erleiden nicht nur Kosten in der Bandbreite, sondern auch doppelte doppelte Abteilungsobjekte und wir müssen ein SET oder DISTINCT_ROOT_ENTITY zu duplizieren.

Doppelte Daten in einer niedrigeren Latenz sind jedoch in vielen Fällen ein guter Kompromiss, wie Markus Winand sagt .

  

Ein SQL-Join ist immer noch effizienter als der geschachtelte Auswahl-Ansatz - obwohl er die gleichen Index-Lookups durchführt - weil er eine Menge Netzwerkkommunikation vermeidet . Es ist sogar noch schneller, wenn die Gesamtmenge der übertragenen Daten wegen der Verdoppelung der Mitarbeiterattribute für jeden Verkauf größer ist. Dies liegt an den zwei Dimensionen der Leistung: Reaktionszeit und Durchsatz; In Computernetzen nennen wir sie Latenz und Bandbreite. Die Bandbreite hat nur geringe Auswirkungen auf die Antwortzeit, aber Latenzen haben eine große Auswirkung . Das bedeutet, dass die Anzahl der Datenbankumläufe für die Antwortzeit wichtiger ist als die Menge der übertragenen Daten.

Das Hauptproblem bei der Verwendung von SUBSELECT ist also, schwer zu kontrollieren und möglicherweise einen ganzen Graphen von Entitäten zu laden in Erinnerung. Beim Batch-Abruf holen Sie die assoziierte Entität in einer separaten Abfrage als SUBSELECT (damit Sie keine Duplikate erleiden), schrittweise und am wichtigsten, dass Sie nur verwandte Entitäten abfragen (damit Sie nicht unter einem riesigen Graphen leiden), weil der IN Die Unterabfrage wird nach den IDs gefiltert, die von der externen Abfrage abgerufen wurden.)

%Vor%

(Es kann ein interessanter Test sein, wenn das Batch-Holen mit einer sehr hohen Batch-Größe wie ein SUBSELECT funktionieren würde, aber ohne das Problem, die gesamte Tabelle zu laden)

Ein paar Posts, die die verschiedenen Abrufstrategien und die SQL-Logs zeigen (sehr wichtig):

Zusammenfassung:

  • JOIN: vermeidet das Hauptproblem von N + 1-Abfragen, aber es kann doppelte Daten abrufen.
  • SUBSELECT: vermeidet auch N + 1 und dupliziert keine Daten, aber es lädt alle Entitäten des zugehörigen Typs in den Speicher.

Die Tabellen wurden mit ascii-tables erstellt.

    
gabrielgiussi 02.05.2016 13:08
quelle
7

Ich würde sagen, es kommt darauf an ...

Nehmen wir an, Sie haben N Mitarbeiter in einer Abteilung, die D Bytes an Informationen enthält und ein durchschnittlicher Mitarbeiter aus E Bytes besteht. (Bytes sind die Summe der Attributlänge mit etwas Overhead).

Mit der join Strategie führen Sie eine Abfrage durch und übertragen N * (D + E) Daten.

Mit der Unterabfrage Strategie führen Sie 1 + N Abfragen durch, aber nur D + N * E Daten.

In der Regel ist die N + 1-Abfrage die NO GO , wenn das N groß ist, daher wird JOIN bevorzugt.

Aber tatsächlich müssen Sie Ihre Laufleistung zwischen der Anzahl der Abfragen und der Datenübertragung überprüfen.

Beachten Sie, dass ich andere Aspekte nicht als Hibernate-Caching betrachte.

Ein zusätzlicher subtiler Aspekt könnte gültig sein, wenn die employee-Tabelle groß und partitioniert ist - das Löschen von Partitionen auf dem Indexzugriff kommt ebenfalls in Betracht.

    
Marmite Bomber 07.10.2015 07:18
quelle
0

Ein Kunde (Finanzdienstleistungen) von mir hatte ein ähnliches Problem, und er wollte "die Daten in einer einzigen Abfrage erfassen". Nun, ich erklärte, dass es besser ist, mehr als eine Abfrage zu haben, weil:

Bei FetchMode.JOIN wird die Abteilung einmal pro Mitarbeiter aus der Datenbank in die Anwendung übertragen, da die Join-Operation dazu führt, dass die Abteilung pro Mitarbeiter multipliziert wird. Wenn Sie 10 Abteilungen mit jeweils 100 Mitarbeitern haben, würde jede dieser 10 Abteilungen innerhalb einer Abfrage, einfacher SQL, 100 Mal übertragen. So wird jede Abteilung in diesem Fall 99-mal öfter als nötig übertragen, was zu einem Datentransfer-Overhead für die Abteilung führt.

Für Fetchmode SUBSELECT werden zwei Abfragen an die Datenbank gesendet. Einer würde verwendet werden, um die Daten der 1000 Angestellten zu erhalten, einer um die 10 Abteilungen zu bekommen. Das klingt für mich viel effizienter. Sicher würden Sie sicherstellen, dass Indizes vorhanden sind, so dass Daten sofort abgerufen werden können.

Ich würde FetchMode.SUBSELECT bevorzugen.

Es wäre ein anderer Fall, wenn jede Abteilung nur einen Angestellten hat, aber, wie der Name "Abteilung" andeutet, wäre dies sehr unwahrscheinlich.

Ich schlage vor, die Zugriffszeiten zu messen, um diese Theorie zu unterstützen. Für meinen Kunden habe ich Messungen für verschiedene Arten von Zugriffen durchgeführt, und die "Abteilung" -Tabelle für meinen Kunden hatte viel mehr Felder (ich habe es jedoch nicht entworfen). So war es schnell klar, dass der FetchMode.SUBSELECT viel schneller war.

    
michaeak 02.08.2017 07:03
quelle
0

sagte Planky

  

(1) Dies ist grob irreführend. (2) Der Subselect wird nicht Ihre gesamte Datenbank in den Speicher holen. Der verlinkte Artikel handelt von einer Eigenart, in der subselect (3) Seitenwechselbefehle vom Elternteil ignoriert, (4) aber immer noch ein Subselect ist.

  1. Nach Ihrem Kommentar habe ich erneut nach FetchMode.SUBSELECT gesucht und herausgefunden, dass meine Antwort nicht ganz korrekt ist.
  2. Dies war eine hypothetische Situation, in der die Hydration jeder Entität, die vollständig in den Speicher geladen wurde (in diesem Fall der Mitarbeiter), viele andere Entitäten hydratisieren wird. Das wahre Problem ist das Laden der gesamten untergeordneten Tabelle, wenn diese Tabelle Tausende von Zeilen enthält (auch wenn nicht jeder andere Entitäten aus anderen Tabellen holt ).
  3. Ich weiß nicht, was Sie mit Paging-Befehlen von den Eltern meinen.
  4. Ja, es ist immer noch ein Subselect, aber ich weiß nicht, worauf Sie damit hinweisen möchten.
  

Die Konsolenausgabe, die Sie über fetchmode_select gepostet haben, ist seltsam, weil dies nicht die Art ist, die funktionieren sollte.

Das ist richtig, aber nur, wenn es mehr als nur Department-Entitäten gibt, die hidrated sind (was bedeutet, dass mehr als eine Mitarbeitersammlung nicht initialisiert ist), habe ich es mit 3.6.10.Final und 4.3 .8.Final In Szenarien 2.2 (FetchMode. SUBSELECT hidrating 2 von 3 Abteilungen) und 3.2 (FetchMode.SUBSELECT unterdrückt alle Abteilungen) , SubselectFetch.toSubselectString gibt Folgendes zurück (die Links zu Hibernate-Klassen stammen aus dem Tag 4.3.8.Final):

%Vor%

Diese Unterabfrage wird später verwendet, um die Where-Klausel zu erstellen. OneToManyJoinWalker.initStatementString endet mit

%Vor%

Dann wird die where-Klausel in CollectionJoinWalker.whereString endet mit

%Vor%

Mit dieser Abfrage werden in beiden Fällen alle Mitarbeiter abgerufen und hydriert. Dies ist eindeutig ein Problem in Szenario 2.2, da wir nur die Abteilungen 1 und 2 hydratisieren, aber auch alle Mitarbeiter, auch wenn sie nicht zu diesen Abteilungen gehören (in diesem Fall Mitarbeiter der Abteilung 3), mit Wasser versorgen.

Wenn es in der Sitzung nur eine Department-Entity gibt, die nicht initialisiert ist und deren Mitarbeitersammlung nicht initialisiert ist, entspricht die Abfrage demjenigen, den eatSleepCode geschrieben hat. Schau dir Szenario 1.2

%Vor%

Von FetchStyle

%Vor%

Bis jetzt konnte ich nicht klären, womit Javadoc gemeint ist:

  

basierend auf der SQL-Einschränkung, die zum Laden des Eigentümers verwendet wurde   

UPDATE Planky sagte:

  

Stattdessen wird die Tabelle im schlimmsten Fall geladen, und selbst dann nur dann, wenn Ihre ursprüngliche Abfrage keine where-Klausel enthielt . Ich würde also sagen, dass die Verwendung von Subselect-Abfragen die gesamte Tabelle unerwartet laden kann, wenn Sie die Ergebnisse LIMITIEREN und Sie keine WHERE-Kriterien haben.

Das ist wahr und es ist ein sehr wichtiges Detail, das ich in der neuen Szenario 4.2

Die zum Abrufen von Mitarbeitern generierte Abfrage ist

%Vor%

Die Unterabfrage in der where-Klausel enthält die ursprüngliche Einschränkung this_.department_name & gt; =? , wodurch die Belastung aller Mitarbeiter vermieden wird. Dies ist, was das Javadoc mit

bedeutet
  

basierend auf der SQL-Einschränkung, die zum Laden des Eigentümers verwendet wurde

Alles was ich über FetchMode gesagt habe.JOIN und die Unterschiede mit FetchMode.SUBSELECT bleibt wahr (und gilt auch für FetchMode.SELECT).

    
gabrielgiussi 03.09.2017 23:53
quelle

Tags und Links