Het “N+1-selecteert probleem” wordt over het algemeen genoemd als een probleem in discussies over object-relationele mapping (ORM), en ik begrijp dat het iets te maken heeft met het moeten maken van veel databasequery’s voor iets dat eenvoudig lijkt in de objectwereld.
Heeft iemand een meer gedetailleerde uitleg van het probleem?
Antwoord 1, autoriteit 100%
Stel dat u een verzameling Car
-objecten (databaserijen) heeft en dat elke Car
een verzameling Wheel
-objecten heeft (ook rijen ). Met andere woorden, Car
→ Wheel
is een 1-op-veel-relatie.
Laten we zeggen dat je alle auto’s moet doorlopen en voor elke auto een lijst met wielen moet afdrukken. De naïeve O/R-implementatie zou het volgende doen:
SELECT * FROM Cars;
En dan voor elke Car
:
SELECT * FROM Wheel WHERE CarId = ?
Met andere woorden, je hebt één selectie voor de auto’s en vervolgens N extra selecties, waarbij N het totale aantal auto’s is.
Als alternatief zou je alle wielen kunnen pakken en de zoekopdrachten in het geheugen kunnen uitvoeren:
SELECT * FROM Wheel
Dit vermindert het aantal retourvluchten naar de database van N+1 naar 2.
De meeste ORM-tools bieden u verschillende manieren om N+1-selecties te voorkomen.
Referentie: Java Persistence with Hibernate, hoofdstuk 13.
Antwoord 2, autoriteit 10%
SELECT
table1.*
, table2.*
INNER JOIN table2 ON table2.SomeFkId = table1.SomeId
Dat krijgt u een resultaat instellen waar kinderrijen in tabel2 duplicatie veroorzaken door de tabel1-resultaten voor elke kindrij in Tabel2 te retourneren. O / R Mappers moeten tabel1-instanties onderscheiden op basis van een uniek sleutelveld, vervolgens alle tabel2-kolommen gebruiken om kinderinstanties te vullen.
SELECT table1.*
SELECT table2.* WHERE SomeFkId = #
De N + 1 is waar de eerste query het primaire object vult en de tweede query alle kinderobjecten vult voor elk van de unieke primaire objecten die worden geretourneerd.
Overweeg:
class House
{
int Id { get; set; }
string Address { get; set; }
Person[] Inhabitants { get; set; }
}
class Person
{
string Name { get; set; }
int HouseId { get; set; }
}
en tabellen met een vergelijkbare structuur. Een enkele query voor het adres “22 valley st” kan terugkeren:
Id Address Name HouseId
1 22 Valley St Dave 1
1 22 Valley St John 1
1 22 Valley St Mike 1
De O / RM moet een exemplaar van thuis vullen met ID = 1, adres = “22 vallei st” en vervolgens de array van de inwoners vullen met mensen voor Dave, John en Mike met slechts één query.
A N + 1-query voor hetzelfde adres dat hierboven is gebruikt, zou resulteren in:
Id Address
1 22 Valley St
Met een afzonderlijke query zoals
SELECT * FROM Person WHERE HouseId = 1
en resulterend in een afzonderlijke gegevensset zoals
Name HouseId
Dave 1
John 1
Mike 1
en het eindresultaat als hetzelfde als hierboven met de Single Query.
De voordelen voor Single Select is dat u alle gegevens voor de voorkant krijgt die mogelijk is wat u uiteindelijk verlangt. De voordelen aan N + 1 zijn querycomplexiteit wordt verminderd en u kunt luie laden gebruiken waar de instellingen van het kindresultaten alleen op eerste verzoek worden geladen.
Antwoord 3, autoriteit 8%
Wat is het N+1-queryprobleem
Het N+1-queryprobleem treedt op wanneer het raamwerk voor gegevenstoegang N extra SQL-instructies heeft uitgevoerd om dezelfde gegevens op te halen die hadden kunnen worden opgehaald bij het uitvoeren van de primaire SQL-query.
Hoe groter de waarde van N, hoe meer query’s worden uitgevoerd, hoe groter de impact op de prestaties. En, in tegenstelling tot het log met trage zoekopdrachten, waarmee u langzaam lopende zoekopdrachten kunt vinden, zal het N+1-probleem niet worden opgelost, omdat elke afzonderlijke aanvullende zoekopdracht voldoende snel wordt uitgevoerd om het log met trage zoekopdrachten niet te activeren.
Het probleem is het uitvoeren van een groot aantal aanvullende zoekopdrachten die over het algemeen voldoende tijd in beslag nemen om de reactietijd te vertragen.
Laten we bedenken dat we de volgende databasetabellen post en post_comments hebben die een één-op-veel-tabelrelatie vormen:
We gaan de volgende 4 post
rijen maken:
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence - Part 1', 1)
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence - Part 2', 2)
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence - Part 3', 3)
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence - Part 4', 4)
En we zullen ook 4 post_comment
onderliggende records maken:
INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Excellent book to understand Java Persistence', 1)
INSERT INTO post_comment (post_id, review, id)
VALUES (2, 'Must-read for Java developers', 2)
INSERT INTO post_comment (post_id, review, id)
VALUES (3, 'Five Stars', 3)
INSERT INTO post_comment (post_id, review, id)
VALUES (4, 'A great reference book', 4)
N+1-queryprobleem met gewone SQL
Als u de post_comments
selecteert met deze SQL-query:
List<Tuple> comments = entityManager.createNativeQuery("""
SELECT
pc.id AS id,
pc.review AS review,
pc.post_id AS postId
FROM post_comment pc
""", Tuple.class)
.getResultList();
En later besluit je de bijbehorende post
title
op te halen voor elke post_comment
:
for (Tuple comment : comments) {
String review = (String) comment.get("review");
Long postId = ((Number) comment.get("postId")).longValue();
String postTitle = (String) entityManager.createNativeQuery("""
SELECT
p.title
FROM post p
WHERE p.id = :postId
""")
.setParameter("postId", postId)
.getSingleResult();
LOGGER.info(
"The Post '{}' got this review '{}'",
postTitle,
review
);
}
Je gaat het probleem met de N+1-query activeren omdat je in plaats van één SQL-query 5 (1 + 4) hebt uitgevoerd:
SELECT
pc.id AS id,
pc.review AS review,
pc.post_id AS postId
FROM post_comment pc
SELECT p.title FROM post p WHERE p.id = 1
-- The Post 'High-Performance Java Persistence - Part 1' got this review
-- 'Excellent book to understand Java Persistence'
SELECT p.title FROM post p WHERE p.id = 2
-- The Post 'High-Performance Java Persistence - Part 2' got this review
-- 'Must-read for Java developers'
SELECT p.title FROM post p WHERE p.id = 3
-- The Post 'High-Performance Java Persistence - Part 3' got this review
-- 'Five Stars'
SELECT p.title FROM post p WHERE p.id = 4
-- The Post 'High-Performance Java Persistence - Part 4' got this review
-- 'A great reference book'
Bevestiging Het N + 1-query-probleem is heel eenvoudig. Het enige wat u hoeft te doen, is alle gegevens die u nodig hebt in de originele SQL-query, zoals deze:
List<Tuple> comments = entityManager.createNativeQuery("""
SELECT
pc.id AS id,
pc.review AS review,
p.title AS postTitle
FROM post_comment pc
JOIN post p ON pc.post_id = p.id
""", Tuple.class)
.getResultList();
for (Tuple comment : comments) {
String review = (String) comment.get("review");
String postTitle = (String) comment.get("postTitle");
LOGGER.info(
"The Post '{}' got this review '{}'",
postTitle,
review
);
}
Deze keer wordt slechts één SQL-query uitgevoerd om alle gegevens te halen die we verder geïnteresseerd zijn in het gebruik.
N + 1 Query-probleem met JPA en HIBERNATE
Bij gebruik van JPA en Hibernate zijn er verschillende manieren waarop u de N + 1-query-probleem kunt triggeren, dus het is erg belangrijk om te weten hoe u deze situaties kunt vermijden.
Overweeg voor de volgende voorbeelden, overweeg we de post
en post_comments
Tabellen naar de volgende entiteiten:
De JPA-toewijzingen zien er als volgt uit:
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
private Long id;
private String title;
//Getters and setters omitted for brevity
}
@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {
@Id
private Long id;
@ManyToOne
private Post post;
private String review;
//Getters and setters omitted for brevity
}
FetchType.EAGER
Het is een slecht idee om FetchType.EAGER
impliciet of expliciet te gebruiken voor uw JPA-associaties, omdat u veel meer gegevens gaat ophalen die u nodig hebt. Bovendien is de FetchType.EAGER
-strategie ook gevoelig voor N+1-queryproblemen.
Helaas gebruiken de koppelingen @ManyToOne
en @OneToOne
standaard FetchType.EAGER
, dus als uw toewijzingen er als volgt uitzien:
@ManyToOne
private Post post;
U gebruikt de FetchType.EAGER
-strategie en elke keer dat u vergeet JOIN FETCH
te gebruiken bij het laden van sommige PostComment
-entiteiten met een JPQL of Criteria API-query:
List<PostComment> comments = entityManager
.createQuery("""
select pc
from PostComment pc
""", PostComment.class)
.getResultList();
U gaat het probleem met de N+1-query activeren:
SELECT
pc.id AS id1_1_,
pc.post_id AS post_id3_1_,
pc.review AS review2_1_
FROM
post_comment pc
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 1
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 2
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 3
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 4
Let op de extra SELECT-instructies die worden uitgevoerd omdat de koppeling post
moet worden opgehaald voordat de List
met PostComment
-entiteiten wordt geretourneerd.
In tegenstelling tot het standaard ophaalplan, dat u gebruikt bij het aanroepen van de find
-methode van de EnrityManager
, definieert een JPQL- of Criteria API-query een expliciet plan dat Hibernate niet kan wijzigen door automatisch een JOIN FETCH te injecteren. U moet het dus handmatig doen.
Als u niet de post
Association hebt gekregen, heeft u veel geluk bij het gebruik van FetchType.EAGER
omdat er geen manier is om het te vermijden. Daarom is het beter om FetchType.LAZY
standaard te gebruiken.
Maar als u post
Association wilt gebruiken, kunt u JOIN FETCH
gebruiken om het N + 1-queryprobleem te vermijden:
List<PostComment> comments = entityManager.createQuery("""
select pc
from PostComment pc
join fetch pc.post p
""", PostComment.class)
.getResultList();
for(PostComment comment : comments) {
LOGGER.info(
"The Post '{}' got this review '{}'",
comment.getPost().getTitle(),
comment.getReview()
);
}
Deze keer voert Hibernate een enkele SQL-instructie uit:
SELECT
pc.id as id1_1_0_,
pc.post_id as post_id3_1_0_,
pc.review as review2_1_0_,
p.id as id1_0_1_,
p.title as title2_0_1_
FROM
post_comment pc
INNER JOIN
post p ON pc.post_id = p.id
-- The Post 'High-Performance Java Persistence - Part 1' got this review
-- 'Excellent book to understand Java Persistence'
-- The Post 'High-Performance Java Persistence - Part 2' got this review
-- 'Must-read for Java developers'
-- The Post 'High-Performance Java Persistence - Part 3' got this review
-- 'Five Stars'
-- The Post 'High-Performance Java Persistence - Part 4' got this review
-- 'A great reference book'
FetchType.LAZY
Zelfs als u overschakelt naar het gebruik van FetchType.LAZY
Expliciet voor alle associaties, kunt u nog steeds tegen het N + 1-probleem komen.
Deze keer, de post
Association wordt in kaart gebracht:
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
Als u nu de entiteiten PostComment
ophaalt:
List<PostComment> comments = entityManager
.createQuery("""
select pc
from PostComment pc
""", PostComment.class)
.getResultList();
Hibernate voert een enkele SQL-instructie uit:
SELECT
pc.id AS id1_1_,
pc.post_id AS post_id3_1_,
pc.review AS review2_1_
FROM
post_comment pc
Maar als je daarna gaat verwijzen naar de luie geladen post
-associatie:
for(PostComment comment : comments) {
LOGGER.info(
"The Post '{}' got this review '{}'",
comment.getPost().getTitle(),
comment.getReview()
);
}
U krijgt het probleem met de N+1-query:
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 1
-- The Post 'High-Performance Java Persistence - Part 1' got this review
-- 'Excellent book to understand Java Persistence'
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 2
-- The Post 'High-Performance Java Persistence - Part 2' got this review
-- 'Must-read for Java developers'
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 3
-- The Post 'High-Performance Java Persistence - Part 3' got this review
-- 'Five Stars'
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 4
-- The Post 'High-Performance Java Persistence - Part 4' got this review
-- 'A great reference book'
Omdat de post
-koppeling lui wordt opgehaald, wordt een secundaire SQL-instructie uitgevoerd bij toegang tot de luie koppeling om het logbericht op te bouwen.
Nogmaals, de oplossing bestaat uit het toevoegen van een JOIN FETCH
-clausule aan de JPQL-query:
List<PostComment> comments = entityManager.createQuery("""
select pc
from PostComment pc
join fetch pc.post p
""", PostComment.class)
.getResultList();
for(PostComment comment : comments) {
LOGGER.info(
"The Post '{}' got this review '{}'",
comment.getPost().getTitle(),
comment.getReview()
);
}
En, net als in het voorbeeld van FetchType.EAGER
, genereert deze JPQL-query een enkele SQL-instructie.
Zelfs als u
FetchType.LAZY
gebruikt en niet verwijst naar de onderliggende associatie van een bidirectionele@OneToOne
JPA-relatie, kunt u nog steeds de N+1-query activeren probleem.
Het probleem met de N+1-query automatisch detecteren
Als u automatisch een N+1-queryprobleem in uw gegevenstoegangslaag wilt detecteren, kunt u de db-util
open source-project.
Eerst moet u de volgende Maven-afhankelijkheid toevoegen:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>db-util</artifactId>
<version>${db-util.version}</version>
</dependency>
Daarna hoeft u alleen het hulpprogramma SQLStatementCountValidator
te gebruiken om de onderliggende SQL-instructies die worden gegenereerd, te bevestigen:
SQLStatementCountValidator.reset();
List<PostComment> comments = entityManager.createQuery("""
select pc
from PostComment pc
""", PostComment.class)
.getResultList();
SQLStatementCountValidator.assertSelectCount(1);
Als u FetchType.EAGER
gebruikt en de bovenstaande testcase uitvoert, krijgt u de volgende testcasefout:
SELECT
pc.id as id1_1_,
pc.post_id as post_id3_1_,
pc.review as review2_1_
FROM
post_comment pc
SELECT p.id as id1_0_0_, p.title as title2_0_0_ FROM post p WHERE p.id = 1
SELECT p.id as id1_0_0_, p.title as title2_0_0_ FROM post p WHERE p.id = 2
-- SQLStatementCountMismatchException: Expected 1 statement(s) but recorded 3 instead!
Antwoord 4, autoriteit 5%
Leverancier met een één-op-veel relatie met Product. Eén leverancier heeft (levert) veel producten.
***** 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 |
+-----+-----------+--------------------+-------+------------+
Factoren:
-
Luie modus voor leverancier ingesteld op “true” (standaard)
-
De ophaalmodus die wordt gebruikt voor zoekopdrachten op het product is Selecteren
-
Ophaalmodus (standaard): Leveranciersinformatie is toegankelijk
-
Caching speelt voor het eerst geen rol bij de
-
Leverancier is toegankelijk
Ophaalmodus is Ophalen selecteren (standaard)
// 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=?
Resultaat:
- 1 select-statement voor Product
- N selecteer verklaringen voor leverancier
Dit is een N+1 selectieprobleem!
Antwoord 5, autoriteit 3%
Ik kan niet direct reageren op andere antwoorden, omdat ik niet genoeg reputatie heb. Maar het is vermeldenswaard dat het probleem zich in wezen alleen voordoet omdat, historisch gezien, veel dbms behoorlijk slecht waren als het gaat om het afhandelen van joins (MySQL is een bijzonder opmerkelijk voorbeeld). Dus n+1 is vaak opmerkelijk sneller geweest dan een join. En dan zijn er manieren om n+1 te verbeteren, maar nog steeds zonder dat je een join nodig hebt, en dat is waar het oorspronkelijke probleem mee te maken heeft.
MySQL is nu echter een stuk beter dan vroeger als het op joins aankomt. Toen ik voor het eerst MySQL leerde, gebruikte ik veel joins. Toen ontdekte ik hoe traag ze zijn, en schakelde in plaats daarvan over naar n+1 in de code. Maar sinds kort ben ik weer overgestapt op joins, omdat MySQL er nu een stuk beter mee omgaat dan toen ik het voor het eerst begon te gebruiken.
Tegenwoordig is een simpele join op een correct geïndexeerde set tabellen zelden een probleem, in termen van prestatie. En als het een prestatiehit oplevert, lost het gebruik van indexhints ze vaak op.
Dit wordt hier besproken door een van de MySQL-ontwikkelteams:
http://jorgenloland.blogspot.co.uk/2013/02/dbt-3-q3-6-x-performance-in-mysql-5610.html
De samenvatting is dus: als je in het verleden joins hebt vermeden vanwege de slechte prestaties van MySQL, probeer het dan opnieuw met de nieuwste versies. Je zult waarschijnlijk aangenaam verrast zijn.
Antwoord 6, autoriteit 2%
We zijn vanwege dit probleem weggegaan van de ORM in Django. Kortom, als je het probeert en doet
for p in person:
print p.car.colour
De ORM retourneert graag alle mensen (meestal als instanties van een Persoonsobject), maar dan moet het de autotabel voor elke Persoon opvragen.
Een eenvoudige en zeer effectieve benadering hiervoor is iets dat ik “fanfolding” noem, waarmee het onzinnige idee wordt vermeden dat queryresultaten van een relationele database moeten worden terugverwezen naar de oorspronkelijke tabellen waaruit de query is afgeleid. samengesteld.
Stap 1: Brede selectie
select * from people_car_colour; # this is a view or sql function
Dit geeft iets terug als
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
Stap 2: Objectiveren
Zoek de resultaten op in een generiek object-maker met een argument om te splitsen na het derde item. Dit betekent dat het “jones”-object niet meer dan één keer zal worden gemaakt.
Stap 3: Renderen
for p in people:
print p.car.colour # no more car queries
Zie deze webpaginavoor een implementatie van fanfoldingvoor python.
Antwoord 7, autoriteit 2%
Hier is een goede beschrijving van het probleem
Nu u het probleem begrijpt, kan het doorgaans worden vermeden door een lid te worden in uw vraag. Dit dwingt in feite de fetch van het luie geladen object, zodat de gegevens in één query worden opgehaald in plaats van N + 1-query’s. Ik hoop dat dit helpt.
8, Autoriteit 2%
Stel dat u bedrijf en werknemer hebt. Het bedrijf heeft veel werknemers (d.w.z. medewerker heeft een veldcompany_id).
In sommige O / R-configuraties, wanneer u een in kaart gebrachte bedrijfsobject hebt en toegang hebt om toegang te krijgen tot zijn werknemersobjecten, zal de O / R-tool er één selecteren voor elke medewerker, waarwel u gewoon dingen in rechte SQL aan het doen was, jij kan select * from employees where company_id = XX
. Dus n (aantal werknemers) plus 1 (bedrijf)
Dit is hoe de eerste versies van EJB-entiteitsbonen werkten. Ik geloof dat dingen zoals overwinning hiermee hebben gedaan, maar ik ben niet zo zeker. De meeste tools bevatten meestal informatie over hun strategie voor de toewijzing.
9
Controleer Ayende Post op het onderwerp: Het bestrijden van het select N + 1-probleem in NHIBERNATE .
In principe, bij gebruik van een ORM, zoals nhibernate of entityframework, als u een relatie in één-op-veel (masterdetails) hebt en alle details per elk masterrecord wilt vermelden, moet u N + 1-query maken Oproepen naar de database, “n” als het aantal masterrecords: 1 query om alle masterrecords en N-query’s, één per master-record te krijgen, om alle details per masterrecord te krijgen.
Meer database-queryoproepen → Meer Latency-tijd → Verlaagde toepassing / databaseprestaties.
ORMS hebben echter opties om dit probleem te voorkomen, voornamelijk met behulp van joins.
10
Het is veel sneller om 1 query af te geven die 100 resultaten retourneert dan om 100 query’s uit te geven die elk 1 resultaat retourneert.
11
Naar mijn mening is het artikel geschreven in Hibernate-valkuil: waarom relaties lui moeten zijn is precies het tegenovergestelde van het echte nummer van N + 1 is.
Als u de juiste uitleg nodig hebt, raadpleegt u HEIBERNATE – HOOFDSTUK 19: Verbetering van de prestaties – Fetching-strategieën
Selecteer het halen (de standaard) is
Zeer kwetsbaar voor n + 1 selecteert
problemen, dus we willen misschien in staat
Doe mee met het ophalen
12
De meegeleverde link heeft een heel eenvoudig voorbeeld van het N + 1-probleem. Als je het toepast op Hibernate, is het in principe over hetzelfde. Wanneer u zoekt voor een object, wordt de entiteit geladen, maar alle verenigingen (tenzij anders geconfigureerd) lui worden geladen. Vandaar één vraag voor de rootobjecten en een andere query om de associaties voor elk van deze te laden. 100 geretourneerde objecten betekent één eerste query en vervolgens 100 aanvullende query’s om de vereniging voor elk, N + 1 te krijgen.
http://pramatr.com/2009/02 / 05 / SQL-N-1-selects-uitgelegd /
13
Eén miljonair heeft n-auto’s. U wilt alle (4) wielen krijgen.
Eén (1) query laadt alle auto’s, maar voor elke (n) auto wordt een afzonderlijke query ingediend voor laadwielen.
Kosten:
Neem aan om indexen in RAM aan te passen.
1 + N query ontleden en schaven + index zoeken EN 1 + N + (N * 4) plaattoegang voor het laden van nuttige lading.
Stel dat indexen niet in ram passen.
Extra kosten in het slechtste geval 1 + N plaattoegangen voor laadindex.
Samenvatting
Fleshals is plaattoegang (ca. 70 keer per seconde willekeurige toegang op hdd)
Een enthousiaste join-select zou ook 1 + N + (N * 4) keer toegang hebben tot de plaat voor nuttige lading.
Dus als de indexen in ram passen – geen probleem, het is snel genoeg omdat er alleen ram-bewerkingen bij betrokken zijn.
Antwoord 14
N+1 select probleem is lastig, en het is logisch om dergelijke gevallen in unit tests te detecteren.
Ik heb een kleine bibliotheek ontwikkeld voor het verifiëren van het aantal query’s dat is uitgevoerd door een bepaalde testmethode of gewoon een willekeurig codeblok – JDBC Sniffer
Voeg gewoon een speciale JUnit-regel toe aan uw testklasse en plaats een annotatie met het verwachte aantal zoekopdrachten op uw testmethoden:
@Rule
public final QueryCounter queryCounter = new QueryCounter();
@Expectation(atMost = 3)
@Test
public void testInvokingDatabase() {
// your JDBC or JPA code
}
15
Zonder inch-stapel implementatiedetails te gaan, zijn er architectonisch gezien dat er minstens twee oplossingen zijn voor N + 1-probleem:
- Heb maar 1 – Big Query – met joins. Dit levert veel informatie uit de database naar de applicatielaag, vooral als er meerdere kinderrecords zijn. Het typische resultaat van een database is een reeks rijen, geen grafiek van objecten (er zijn oplossingen voor die met verschillende DB-systemen)
- Heb twee (of meer voor meer kinderen moesten worden toegevoegd) Query’s – 1 voor de ouder en nadat je ze hebt – doorzoek de kinderen op ID’s en breng ze in kaart. Dit minimaliseert de gegevensoverdracht tussen de DB- en APP-lagen.
Antwoord 16
Neem het voorbeeld van Matt Solnit, stel je voor dat je een associatie tussen Car en Wheels als LAZY definieert en dat je een aantal Wheels-velden nodig hebt. Dit betekent dat de slaapstand na de eerste selectie “Select * from Wheels where car_id = :id” voor ELKE auto gaat doen.
Dit maakt de eerste selectie en meer 1 selectie door elke N auto, daarom wordt het n+1 probleem genoemd.
Om dit te voorkomen, moet u ervoor zorgen dat de koppeling zo gretig wordt opgehaald, zodat de slaapstand gegevens laadt met een join.
Maar let op, als je de bijbehorende Wheels vaak niet gebruikt, is het beter om het LAZY te houden of het ophaaltype te wijzigen met Criteria.
Antwoord 17
N+1 SELECT-probleem is echt moeilijk te herkennen, vooral in projecten met een groot domein, tot het moment dat het de prestaties begint te verminderen. Zelfs als het probleem is opgelost, d.w.z. door gretig laden toe te voegen, kan een verdere ontwikkeling de oplossing breken en/of het N+1 SELECT-probleem op andere plaatsen opnieuw introduceren.
Ik heb een open source-bibliotheek jplusonegemaakt om die problemen in op JPA gebaseerde Spring Boot Java-toepassingen aan te pakken . De bibliotheek biedt twee belangrijke functies:
- Genereert rapporten die SQL-statements correleren met uitvoeringen van JPA-bewerkingen die ze hebben getriggerd en plaatst in de broncode van uw applicatie die erbij betrokken was
2020-10-22 18:41:43.236 DEBUG 14913 --- [ main] c.a.j.core.report.ReportGenerator: WORTEL com.adgadev.jplusone.test.domain.bookshop.BookshopControllerTest.shouldGetBookDetailsLazily(BookshopControllerTest.java:65) com.adgadev.jplusone.test.domain.bookshop.bookshopcontroller.getsampleBookinglazyloading (BookshopController.java:31) com.adgadev.jplusone.test.domain.bookshop.bookshopservice.getSampleBookDetailSusinglazyloading [proxy] Sessiegrens Operatie [impliciet] 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.autuister [fetching entiteit] Verklaring [lees] selecteer uit Auteur Author0_ Links Outer Join Genre Genre1_ op author0_.genre_id = genre1_.id waar author0_.id = 1 Operatie [impliciet] 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 collectie] Verklaring [lees] selecteer uit Book Books0_ waar Books0_.author_id = 1
- Biedt API die het mogelijk maakt tests te schrijven die controleren hoe effectief uw aanvraag JPA (d.w.z. beweert hoeveelheid luie laadbewerkingen)
@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);
}
}