250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- BOJ
- 지연로딩
- 연관관계
- 연결리스트
- 스토어드 프로시저
- FetchType
- JPQL
- querydsl
- 힙
- shared lock
- SQL프로그래밍
- 다대일
- 비관적락
- eager
- fetch
- PS
- exclusive lock
- 즉시로딩
- 이진탐색
- 백트래킹
- 일대다
- 유니크제약조건
- 다대다
- 낙관적락
- execute
- 동적sql
- dfs
- 스프링 폼
- 데코레이터
- CHECK OPTION
Archives
- Today
- Total
흰 스타렉스에서 내가 내리지
[JPA] N+1 문제 (실은 1+N 문제) 본문
728x90
즉시 로딩은 JPQL 을 실행할 때 N+1 문제가 발생할 수 있다.
그럼, 지연 로딩일 때는 N+1 문제가 발생하지 않는가?
@Entity
public class Order{
...
@ManyToOne(fetch = FetchType.EAGER)
private Member member;
...
}
- 주문 엔티티를 조회하면 연관된 member 엔티티도 항상 함께 로딩된다.
# 글로벌 페치 전략에 즉시 로딩 사용 시 단점
- 사용하지 않는 엔티티를 로딩한다.
- N+1 문제가 발생한다.
# N+1 문제
- em.find() 메소드로 엔티티를 조회할 때 연관된 엔티티를 로딩하는 전략이 즉시 로딩이면, 데이터베이스에 JOIN 쿼리를 사용해서 한 번에 연관된 엔티티까지 조회한다.
Order order = em.find(Order.class, 1L);
// SQL
select o.*, m.*
from Order o
left outer join Member m on o.MEMBER_ID = m.MEMBER_ID
where o.id=1
- 실행된 SQL 을 보면 즉시 로딩으로 설정한 member 엔티티를 JOIN 쿼리로 함께 조회한다.
- 문제는 JPQL 을 사용할 때 발생한다.
List<Order> orders =
em.createQuery("select o from Order o", Order.class)
.getResultList();
// SQL
select * from Order -- JPQL 로 실행된 SQL
select * from Member where id=? -- EAGER 로 실행된 SQL
select * from Member where id=? -- EAGER 로 실행된 SQL
select * from Member where id=? -- EAGER 로 실행된 SQL
select * from Member where id=? -- EAGER 로 실행된 SQL
- JPA 가 JPQL 을 분석해서 SQL 을 생성할 때는 글로벌 페치 전략을 참고하지 않고 오직 JPQL 자체만 사용한다.
- 따라서 즉시로딩이든 지연로딩이든 구분하지 않고 JPQL 쿼리 자체에 충실하게 SQL 을 만든다.
다음과 같은 순서로 동작한다.
- select o from Order o JPQL 을 분석해서 select * from Order SQL을 생성한다.
- 데이터베이스에서 결과를 받아 order 엔티티 인스턴스들을 생성한다.
- order.member 의 글로벌 페치 전략이 즉시 로딩이므로 order 를 로딩하는 즉시 연관된 member 도 로딩해야 한다.
- 연관된 member 를 영속성 컨텍스트에서 찾는다
- 만약 영속성 컨텍스트에 없으면 SELECT * FROM MEMBER WHERE id=? SQL 을 조회한 order 엔티티 수만큼 실행한다.
- 만약 조회한 order 엔티티가 10개이면 member 를 조회하는 SQL 도 10번 실행한다.
- 이처럼 처음 조회한 데이터 수만큼 다시 SQL 을 사용해서 조회하는 것을 N+1 문제라 한다.
- N+1 이 발생하면 SQL 이 상당히 많이 호출되므로 조회 성능에 치명적인다.
- 이런 N+1 문제는 JPQL 페치 조인으로 해결할 수 있다.
결론 :
N+1 문제는, 연관된 엔티티가 즉시로딩인 엔티티를 JPQL 로 호출할 때,
조회한 데이터 수(N)만큼 다시 SQL 을 사용해서 조회하는 것을 말한다.
이는 JPQL 페치 조인으로 해결할 수 있다.
그럼, 지연 로딩일 때는 N+1 문제가 발생하지 않는가?
for (Member member : members){
System.out.println("member = " + member.getOrders().size());
}
문제는 위처럼 모든 회원에 대해 연관된 주문 컬렉션을 사용할 떄 발생한다.
주문 컬렉션을 초기화 하는 수만큼 다음 SQL 이 실행될 수 있다.
회원이 5명이면 회원에 따른 주문도 5번 조회된다.
SELECT * FROM ORDERS WHERE MEMBER_ID = 1
SELECT * FROM ORDERS WHERE MEMBER_ID = 2
SELECT * FROM ORDERS WHERE MEMBER_ID = 3
SELECT * FROM ORDERS WHERE MEMBER_ID = 4
SELECT * FROM ORDERS WHERE MEMBER_ID = 5
이것도 결국 N+1 문제다.
N+1 문제는 즉시 로딩과 지연 로딩일 때 모두 발생할 수 있다.
https://thisisjoos.tistory.com/715
[JPA] N+1 문제를 피할 수 있는 다양한 방법
1. 페치 조인 사용가장 일반적인 방법select m from Member m join fetch m.ordersSELECT M.*, O.* FROM MEMBER MINNER JOIN ORDERS O ON M.ID=O.MEMBER_ID 일대다 조인을 했으므로 중복된 결과가 나타날 수 있다.따라서 JPQL 의 DI
thisisjoos.tistory.com
'JPA' 카테고리의 다른 글
[JPA] Spring-data-JPA 의 메소드 이름으로 쿼리 생성 (0) | 2024.04.25 |
---|---|
[JPA] 엔티티의 데이터를 변환해서 DB 에 저장하는 @Converter (0) | 2024.04.25 |
트랜잭션 범위의 영속성 컨텍스트 (0) | 2024.04.25 |
[JPA] 영속성 컨텍스트와 JPQL (0) | 2024.04.25 |
[Querydsl] 스프링 데이터 페이징 (0) | 2024.04.20 |