N+1 문제란

N+1 문제는 ORM(Object-Relational Mapping)에서 자주 발생하는 성능 문제 중 하나로, 연관된 엔티티를 조회할 때, 연관된 엔티티의 수(N)만큼 추가로 쿼리가 실행되는 문제이다. 그 결과, 쿼리의 수가 N+1개가 된다. 쿼리의 수가 많아지면 데이터베이스와의 통신이 늘어나고, 성능이 저하될 수 있다.

그렇기 때문에 N+1 문제는 성능 최적화를 위해 주의해야 하는 문제이다.

동작 원리

  1. 첫 번째 쿼리로 엔티티를 조회한다.
1
List<Member> members = memberRepository.findAll();
  1. 조회된 엔티티를 사용할 때마다 추가로 쿼리가 실행된다.
1
2
3
for (Member member : members) {
    System.out.println(member.getTeam().getName());
}
  1. 연관된 엔티티의 수만큼 추가로 쿼리가 실행된다.
1
2
3
4
SELECT * FROM Team WHERE team_id = 1;
SELECT * FROM Team WHERE team_id = 2;
SELECT * FROM Team WHERE team_id = 3;
...

해결 방법

Eager Loading으로 N+1 문제를 해결하려고 하면 성능이 저하될 수 있다. 따라서 Fetch Join, Batch Fetch, EntityGraph 등을 사용하여 해결하는 것이 좋다.

N+1 문제를 해결하는 방법은 다음과 같다.

  1. Fetch Join 사용: 연관된 엔티티를 함께 조회한다.
1
2
@Query("SELECT m FROM Member m JOIN FETCH m.team")
List<Member> findAllWithTeam();
  1. Batch Fetch 사용: 연관된 엔티티를 한꺼번에 조회한다.
1
2
3
4
5
6
@Entity
public class Member {
    @ManyToOne(fetch = FetchType.LAZY)
    @BatchSize(size = 100)
    private Team team;
}
  1. EntityGraph 사용: 연관된 엔티티를 함께 조회한다.
1
2
3
4
// 이 방식은 JPA 2.1부터 지원된다.
@EntityGraph(attributePaths = {"team"})
@Query("SELECT m FROM Member m")
List<Member> findAllWithTeam();

결론

N+1 문제는 연관된 엔티티를 조회할 때 발생하는 성능 문제이다. 이 문제를 해결하기 위해서는 Fetch Join, Batch Fetch, EntityGraph 등을 사용하여 연관된 엔티티를 함께 조회하거나 한꺼번에 조회하는 방법을 사용할 수 있다.