데이터베이스와 관련된 성능 이슈로, JPA나 ORM을 사용할때 자주 발생한다
한번의 쿼리로 해결할 수 있는 작업이 여러번의 쿼리로 나뉘어 실행되면서 성능 저하를 유발하는 문제
동작 과정
1. 1개의 쿼리 실행
- 먼저 1 에 해당하는 쿼리를 실행해 특정 엔티티의 리스트를 가져온다
- 예 : SELECT * FROM orders; (주문 데이터를 조회)
2. N개의 추가 쿼리 실행
- 가져온 각 엔티티와 연관된 데이터를 조회하기 위해 N개의 추가 쿼리가 실행된다
- 예 : 각 주문에 연관된 고객 데이터를 가져오는 쿼리
예제 및 동작 설명
Order 와 Customer 관계 (@ManyToOne) :
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY) // 연관관계 설정
private Customer customer;
}
문제 발생시 동작 :
List<Order> orders = orderRepository.findAll(); // 모든 주문 조회
for (Order order : orders) {
System.out.println(order.getCustomer().getName()); // 각 주문의 고객 이름 출력
}
1. 첫번째 쿼리 :
SELECT * FROM orders;
- 모든 주문 데이터를 조회
2. 추가 N개의 쿼리 :
- 각 주문마다 고객 데이터를 조회하기 위해 실행
SELECT * FROM customer WHERE id = ?; -- 주문 1의 고객
SELECT * FROM customer WHERE id = ?; -- 주문 2의 고객
...
N + 1 문제의 발생 원인
- JPA의 기본 동작 방식 중 하나인 지연 로딩 (Lazy Loading)이 원인
- 연관된 데이터를 사용하는 시점에 추가 쿼리를 실행하기 때문에 발생
해결방법
1. 즉시 로딩(Eager) 사용
- 연관된 데이터를 한번의 쿼리로 가져옴
- 연관 필드에 fetch = FetchType.EAGER 설정
@ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
실행 쿼리 :
SELECT o.*, c.*
FROM orders o
JOIN customer c ON o.customer_id = c.id;
2. 페치 조인(Fetch Join) 사용
- JPQL 에서 JOIN FETCH를 사용해 연관된 엔티티를 한번에 조회
@Query("SELECT o FROM Order o JOIN FETCH o.customer")
List<Order> findAllWithCustomer();
실행 쿼리 :
SELECT o.*, c.*
FROM orders o
JOIN customer c ON o.customer_id = c.id;
3. EntityGraph 사용
- 엔티티 그래프를 사용해 특정 필드를 페치 조인 하도록 설정
@EntityGraph(attributePaths = {"customer"})
@Query("SELECT o FROM Order o")
List<Order> findAllWithCustomer();
4. Batch Size 설정
- @BatchSize 어노테이션이나 hibernate.default_batch_fetch_size 설정을 통해 한번에 여러 데이터를 로드
@BatchSize(size = 10)
@ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
문제 해결 시 주의사항
1. Eager Loading 과잉 사용 금지
- 모든 연관 데이터를 즉시 로딩하면 불필요한 데이터를 가져와 성능 저하를 유발할 수 있음
2. 페치 조인 중복 방지
- 여러 페치 조인을 사용할 경우, JPA에서 데이터 중복이 발생할 수 있음
3. 필요한 데이터만 조회
- 항상 필요한 데이터만 정확히 가져오도록 쿼리를 설계
요약
- N + 1 문제는 한 번의 쿼리로 처리 가능한 데이터를 여러 번의 쿼리로 가져와 성능 저하를 유발하는 문제
- 주로 지연 로딩(Lazy Loading)으로 인해 발생
- 해결 방법 :
- 즉시 로딩(Eager Loading)
- 페치 조인(Fetch Join)
- EntityGraph 사용
- Batch Size 설정
- 최적화 시 필요 이상의 데이터를 불러오지 않도록 주의
출처 : ChatGPT
'BE > Java' 카테고리의 다른 글
[Java] HttpServletRequest (0) | 2025.01.08 |
---|---|
[Java] Jackson 라이브러리 (0) | 2025.01.07 |
[Java] Generic 제너릭 (0) | 2025.01.04 |
[Java] enum (0) | 2025.01.01 |
[Java] @IdClass (0) | 2024.12.31 |