මෙය ඉතා සුලභ ප්රශ්නයක් බැවින්, මම මෙම ලිපිය ලිවූ
අතර, මෙම පිළිතුර පදනම් වී ඇත.
N + 1 විමසුම් ගැටළුව කුමක්ද?
ප්රාථමික SQL විමසුම ක්රියාත්මක කිරීමේදී ලබා ගත හැකි එකම දත්ත ලබා ගැනීම සඳහා දත්ත ප්රවේශ රාමුව N අතිරේක SQL ප්රකාශයන් ක්රියාත්මක කළ විට N + 1 විමසුම් ගැටළුව සිදු වේ.
N හි වටිනාකම විශාල වන තරමට විමසුම් ක්රියාත්මක වන තරමට කාර්ය සාධන බලපෑම වැඩි වේ. මන්දගාමී විමසුම් සොයා ගැනීමට ඔබට උපකාර කළ හැකි මන්දගාමී විමසුම් ලොගය මෙන් නොව , N + 1 නිකුතුව හඳුනාගත නොහැකි වනුයේ මන්දගාමී විමසුම් ලොගය අවුලුවාලීමට එක් එක් අමතර විමසුම් ප්රමාණවත් තරම් වේගයෙන් ධාවනය වන බැවිනි.
ගැටළුව වන්නේ ප්රතිචාර කාලය මන්දගාමී කිරීමට ප්රමාණවත් කාලයක් ගතවන අතිරේක විමසුම් විශාල සංඛ්යාවක් ක්රියාත්මක කිරීමයි.
ගේ අපි පිහිටුවීමට වන පහත සඳහන් තනතුර හා post_comments දත්ත සමුදාය වගු ගැන අපි දැන් සලකා බලමු ඒක-බහු වගුව සම්බන්ධතාවය :

අපි පහත postපේළි 4 නිර්මාණය කිරීමට යන්නෙමු :
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)
තවද, අපි post_commentළමා වාර්තා 4 ක් ද නිර්මාණය කරමු :
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)
සරල SQL සමඟ N + 1 විමසුම් ගැටළුව
ඔබ post_commentsමෙම SQL විමසුම භාවිතා කිරීම තෝරා ගන්නේ නම් :
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();
පසුව, post titleඑක් එක් සඳහා සම්බන්ධිතයන් ලබා ගැනීමට ඔබ තීරණය කරයි 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
);
}
ඔබ N + 1 විමසුම් ගැටළුව අවුලුවාලීමට යන්නේ එක් SQL විමසුමක් වෙනුවට ඔබ 5 (1 + 4) ක්රියාත්මක කළ බැවිනි:
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'
N + 1 විමසුම් ගැටළුව විසඳීම ඉතා පහසුය. ඔබ කළ යුත්තේ මුල් SQL විමසුමේදී ඔබට අවශ්ය සියලුම දත්ත උපුටා ගැනීම පමණි:
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
);
}
අප තවදුරටත් භාවිතා කිරීමට උනන්දුවක් දක්වන සියලුම දත්ත ලබා ගැනීම සඳහා මෙවර ක්රියාත්මක වන්නේ එක් SQL විමසුමක් පමණි.
JPA සහ ශිශිරතාරක සමඟ N + 1 විමසුම් ගැටළුව
JPA සහ Hibernate භාවිතා කරන විට, ඔබට N + 1 විමසුම් ගැටළුව අවුලුවාලිය හැකි ක්රම කිහිපයක් තිබේ, එබැවින් ඔබට මෙම තත්වයන් වළක්වා ගත හැකි ආකාරය දැන ගැනීම ඉතා වැදගත් වේ.
ඊළඟ උදාහරණ සඳහා, අපි පහත වගු වෙත වගු postසහ post_commentsවගු සිතියම් ගත කරන බව සලකන්න :

JPA සිතියම්කරණය මේ වගේ ය:
@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
FetchType.EAGERඔබේ JPA සංගම් සඳහා ව්යංගයෙන් හෝ පැහැදිලිව භාවිතා කිරීම නරක අදහසකි, මන්ද ඔබට අවශ්ය තවත් දත්ත ලබා ගැනීමට ඔබ යන බැවිනි. තවද, FetchType.EAGERඋපායමාර්ගය N + 1 විමසුම් ගැටළු වලටද ගොදුරු වේ.
අවාසනාවට, @ManyToOneසහ @OneToOneසංගම් FetchType.EAGERපෙරනිමියෙන් භාවිතා කරයි, එබැවින් ඔබේ සිතියම්කරණය මේ ආකාරයෙන් පෙනේ නම්:
@ManyToOne
private Post post;
ඔබ FetchType.EAGERඋපාය මාර්ගය භාවිතා කරන අතර , JPQL හෝ නිර්ණායක API විමසුමක් සමඟ JOIN FETCHසමහර PostCommentආයතන පූරණය කිරීමේදී භාවිතා කිරීමට අමතක වූ සෑම අවස්ථාවකම :
List<PostComment> comments = entityManager
.createQuery("""
select pc
from PostComment pc
""", PostComment.class)
.getResultList();
ඔබ N + 1 විමසුම් ගැටළුව අවුලුවාලීමට යන්නේ:
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
මන්ද ක්රියාත්මක කරන බව අතිරේක සිෙලක්ට් ප්රකාශ සලකා බලන්න postසංගමය නැවත පැමිණෙන පෙර ඉහළම තේ විය යුතුය Listක PostCommentආයතනයන්ය.
findක්රමවේදය EnrityManagerඇමතීමේදී ඔබ භාවිතා කරන පෙරනිමි ලබා ගැනීමේ සැලැස්ම මෙන් නොව , JPQL හෝ නිර්ණායක API විමසුමක් මඟින් ස්වයංක්රීයව JOIN FETCH එන්නත් කිරීමෙන් ශිශිරතාරයට වෙනස් කළ නොහැකි පැහැදිලි සැලැස්මක් අර්ථ දක්වයි. එබැවින්, ඔබ එය අතින් කළ යුතුය.
ඔබට postසංගමය කිසිසේත් අවශ්ය නොවූයේ නම් , FetchType.EAGERඑය ලබා ගැනීමෙන් වළක්වා ගැනීමට ක්රමයක් නොමැති නිසා භාවිතා කිරීමේදී ඔබට වාසනාව නැත. FetchType.LAZYපෙරනිමියෙන් භාවිතා කිරීම වඩා හොඳ වන්නේ එබැවිනි .
එහෙත්, ඔබට postඇසුර භාවිතා කිරීමට අවශ්ය නම්, ඔබට JOIN FETCHN + 1 විමසුම් ගැටළුව මඟහරවා ගත හැකිය :
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()
);
}
මෙවර හයිබර්නේට් තනි SQL ප්රකාශයක් ක්රියාත්මක කරයි:
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.EAGERලබා ගැනීමේ උපාය මාර්ගයෙන් ඔබ වැළකී සිටිය යුත්තේ ඇයිද යන්න පිළිබඳ වැඩි විස්තර සඳහා , මෙම ලිපිය ද බලන්න.
FetchType.LAZY
ඔබ FetchType.LAZYසියලු සංගම් සඳහා පැහැදිලිවම භාවිතා කිරීමට මාරු වුවද , ඔබට තවමත් N + 1 නිකුතුවට සම්බන්ධ විය හැකිය.
මෙවර postසංගමය මේ ආකාරයට සිතියම් ගත කර ඇත:
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
දැන්, ඔබ PostCommentආයතන ලබා ගන්නා විට:
List<PostComment> comments = entityManager
.createQuery("""
select pc
from PostComment pc
""", PostComment.class)
.getResultList();
ශිශිරතාරකය තනි SQL ප්රකාශයක් ක්රියාත්මක කරයි:
SELECT
pc.id AS id1_1_,
pc.post_id AS post_id3_1_,
pc.review AS review2_1_
FROM
post_comment pc
එහෙත්, පසුව නම්, ඔබ කම්මැලි බර පටවා ඇති postඇසුර ගැන සඳහන් කිරීමට යන්නේ :
for(PostComment comment : comments) {
LOGGER.info(
"The Post '{}' got this review '{}'",
comment.getPost().getTitle(),
comment.getReview()
);
}
ඔබට N + 1 විමසුම් නිකුතුව ලැබෙනු ඇත:
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'
මෙම නිසා postසංගමයේ ඉහළම තේ lazily වේ, ලඝු-සටහන පණිවිඩය ඉදි කිරීම සඳහා කම්මැලි සංගමය වෙත ප්රවේශ වෙන විට ද්විතීයික SQL ප්රකාශ ක්රියාත්මක කරනු ඇත.
නැවතත්, නිවැරදි JOIN FETCHකිරීම JPQL විමසුමට වගන්තියක් එක් කිරීමෙන් සමන්විත වේ :
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()
);
}
හා, හුදෙක් වගේ FetchType.EAGERඋදාහරණයක් ලෙස, මෙම JPQL විමසුම තනි SQL ප්රකාශ උත්පාදනය කරනු ඇත.
ඔබ භාවිතා කරන්නේ FetchType.LAZYසහ ද්විපාර්ශ්වික @OneToOneJPA සම්බන්ධතාවයක ළමා ඇසුර ගැන සඳහන් නොකලත්, ඔබට තවමත් N + 1 විමසුම් ගැටළුව අවුලුවාලිය හැකිය.
@OneToOneසංගම් විසින් ජනනය කරන ලද N + 1 විමසුම් ගැටළුව මඟහරවා ගත හැකි ආකාරය පිළිබඳ වැඩි විස්තර සඳහා , මෙම ලිපිය බලන්න .
N + 1 විමසුම් ගැටළුව ස්වයංක්රීයව හඳුනා ගන්නේ කෙසේද
ඔබේ දත්ත ප්රවේශ ස්ථරයේ N + 1 විමසුම් ගැටළුව ස්වයංක්රීයව හඳුනා ගැනීමට ඔබට අවශ්ය නම් , විවෘත මූලාශ්ර ව්යාපෘතිය භාවිතයෙන් ඔබට එය කළ හැකි ආකාරය මෙම ලිපියෙන් පැහැදිලි db-utilකෙරේ.
පළමුව, ඔබ පහත දැක්වෙන මාවන් පරායත්තතාව එක් කළ යුතුය:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>db-util</artifactId>
<version>${db-util.version}</version>
</dependency>
පසුව, SQLStatementCountValidatorඋත්පාදනය වන යටින් පවතින SQL ප්රකාශයන් තහවුරු කිරීමට ඔබට උපයෝගීතාව භාවිතා කළ යුතුය :
SQLStatementCountValidator.reset();
List<PostComment> comments = entityManager.createQuery("""
select pc
from PostComment pc
""", PostComment.class)
.getResultList();
SQLStatementCountValidator.assertSelectCount(1);
ඔබ FetchType.EAGERඉහත පරීක්ෂණ නඩුව භාවිතා කර ක්රියාත්මක කරන්නේ නම්, ඔබට පහත සඳහන් පරීක්ෂණ අවස්ථා අසාර්ථක වනු ඇත:
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!
db-utilවිවෘත මූලාශ්ර ව්යාපෘතිය පිළිබඳ වැඩි විස්තර සඳහා මෙම ලිපිය බලන්න .