যেহেতু এটি একটি খুব সাধারণ প্রশ্ন, আমি এই নিবন্ধটি লিখেছি
, যার ভিত্তিতে এই উত্তরটি ভিত্তিক।
এন +1 ক্যোয়ারী সমস্যাটি কী
N + 1 ক্যোয়ারী সমস্যাটি ঘটে যখন ডেটা অ্যাক্সেস ফ্রেমওয়ার্কটি প্রাথমিক এসকিউএল কোয়েরি কার্যকর করার সময় পুনরুদ্ধার করা যেতে পারে একই ডেটা আনার জন্য এন অতিরিক্ত এসকিউএল স্টেটমেন্টগুলি কার্যকর করে।
N এর মান যত বেশি হবে, তত বেশি প্রশ্নগুলি কার্যকর করা হবে, কার্যকারণের প্রভাব তত বেশি। এবং, ধীর চলমান ক্যোয়ারীগুলি সন্ধান করতে আপনাকে সাহায্য করতে পারে এমন ধীর ক্যোয়ারী লগের বিপরীতে , এন + 1 সমস্যাটি স্পট হবে না কারণ প্রতিটি পৃথক অতিরিক্ত ক্যোয়ারী ধীর ক্যোয়ারি লগটি না চালানোর জন্য যথেষ্ট দ্রুত চলে।
সমস্যাটি হ'ল বিপুল সংখ্যক অতিরিক্ত প্রশ্নের সন্ধান করছে যা সামগ্রিকভাবে প্রতিক্রিয়া সময়কে ধীর করতে যথেষ্ট সময় নেয়।
আসুন বিবেচনা করা যাক আমাদের নিম্নলিখিত পোস্ট এবং পোস্ট_কমেন্টস ডাটাবেস টেবিলগুলি রয়েছে যা একের মধ্যে একাধিক টেবিল সম্পর্ক তৈরি করে :
আমরা নিম্নলিখিত 4 টি post
সারি তৈরি করতে যাচ্ছি :
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)
এবং, আমরা 4 টি post_comment
শিশু রেকর্ডও তৈরি করব :
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)
প্লেইন এসকিউএল নিয়ে এন +1 ক্যোয়ারী সমস্যা
আপনি যদি post_comments
এই এসকিউএল কোয়েরি ব্যবহার করে নির্বাচন করেন :
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
);
}
আপনি এন + 1 ক্যোয়ারী সমস্যাটি ট্রিগার করতে চলেছেন কারণ একটি এসকিউএল ক্যোয়ারীর পরিবর্তে, আপনি 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 ক্যোয়ারী ইস্যু ঠিক করা খুব সহজ। আসল এসকিউএল কোয়েরিতে আপনার প্রয়োজনীয় সমস্ত ডেটা উত্তোলন করার জন্য আপনাকে যা করতে হবে তা হ'ল:
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
);
}
এবার আমরা ব্যবহার করতে আরও আগ্রহী সমস্ত ডেটা আনতে কেবলমাত্র একটি এসকিউএল কোয়েরি কার্যকর করা হয়েছে।
জেপিএ এবং হাইবারনেট নিয়ে এন +1 ক্যোয়ারী সমস্যা
জেপিএ এবং হাইবারনেট ব্যবহার করার সময়, আপনি এন + 1 ক্যোয়ারী ইস্যুটি ট্রিগার করতে পারেন এমন বেশ কয়েকটি উপায় রয়েছে, সুতরাং আপনি কীভাবে এই পরিস্থিতিগুলি এড়াতে পারবেন তা জানা খুব গুরুত্বপূর্ণ।
পরবর্তী উদাহরণের জন্য, বিবেচনা আমরা ম্যাপিং হয় post
এবং post_comments
নিম্নলিখিত সত্ত্বা থেকে টেবিল:
জেপিএ ম্যাপিংগুলি এর মতো দেখাচ্ছে:
@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
কৌশলটি এন + 1 ক্যোয়ারী ইস্যুতেও প্রবণ।
দুর্ভাগ্যবশত, @ManyToOne
এবং @OneToOne
সমিতির ব্যবহার FetchType.EAGER
, ডিফল্ট অনুসারে তাই যদি আপনার ম্যাপিং ভালো চেহারা:
@ManyToOne
private Post post;
আপনি FetchType.EAGER
কৌশলটি ব্যবহার করছেন এবং প্রতিবারের মতো JOIN FETCH
কোনও PostComment
সত্তা কোনও জেপিকিউএল বা মানদণ্ডের এপিআই কোয়েরি লোড করার সময় আপনি ব্যবহার করতে ভুলে যাচ্ছেন:
List<PostComment> comments = entityManager
.createQuery("""
select pc
from PostComment pc
""", PostComment.class)
.getResultList();
আপনি এন + 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
, একটি জেপিকিউএল বা ক্রিটারিয়া এপিআই ক্যোয়ারী একটি সুস্পষ্ট পরিকল্পনা সংজ্ঞায়িত করে যে হাইবারনেট স্বয়ংক্রিয়ভাবে একটি জিন ফিচকে ইনজেকশন দিয়ে পরিবর্তন করতে পারে না। সুতরাং, আপনার এটি ম্যানুয়ালি করা দরকার।
আপনার যদি post
অ্যাসোসিয়েশনটি মোটেই প্রয়োজন না হয়, ব্যবহার করার সময় আপনি ভাগ্য থেকে দূরে থাকবেন FetchType.EAGER
কারণ এটি আনা এড়ানোর কোনও উপায় নেই। এজন্য FetchType.LAZY
ডিফল্টরূপে ব্যবহার করা ভাল ।
তবে, আপনি যদি post
অ্যাসোসিয়েশনটি ব্যবহার করতে চান , তবে আপনি JOIN FETCH
N + 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()
);
}
এবার হাইবারনেট একটি একক এসকিউএল বিবৃতি কার্যকর করবে:
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
সমস্ত সংঘের জন্য স্পষ্টভাবে ব্যবহার করতে স্যুইচ করলেও আপনি এখনও এন + 1 ইস্যুতে ঝাঁপিয়ে পড়তে পারেন।
এবার post
সমিতিটি এইভাবে ম্যাপ করা হয়েছে:
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
এখন, আপনি PostComment
সত্ত্বা আনার সময় :
List<PostComment> comments = entityManager
.createQuery("""
select pc
from PostComment pc
""", PostComment.class)
.getResultList();
হাইবারনেট একটি একক এসকিউএল বিবৃতি কার্যকর করবে:
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()
);
}
আপনি এন + 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
সমিতি প্রখর রৌদ্রে সংগৃহীত হয়, সেকেন্ডারি SQL বক্তব্য যখন অর্ডার লগ বার্তা নির্মাণ করার জন্য অলস সমিতি অ্যাক্সেস মৃত্যুদন্ড কার্যকর করা হবে না।
আবার, JOIN FETCH
ফিক্সটি জেপিকিউএল কোয়েরিতে একটি ধারা যোগ করার সাথে জড়িত:
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
যেমন উদাহরণের মতো, এই জেপিকিউএল কোয়েরিটি একটি একক এসকিউএল বিবৃতি উত্পন্ন করবে।
আপনি যদি FetchType.LAZY
দ্বি- @OneToOne
নির্দেশমূলক জেপিএ সম্পর্কের শিশু সংযোগটি ব্যবহার করে থাকেন এবং উল্লেখ না করেন , আপনি এখনও এন + 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
অন্তর্নিহিত এসকিউএল স্টেটমেন্টগুলি উত্পন্ন করার জন্য ইউটিলিটিটি ব্যবহার করতে হবে:
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
ওপেন-সোর্স প্রকল্প সম্পর্কে আরও তথ্যের জন্য , এই নিবন্ধটি দেখুন ।