N+1

mangez_js·2024년 12월 9일

Study

목록 보기
26/47

N+1

조회 시 1개의 쿼리를 생각하고 설계 했으니 나오지 않아도 되는 조회의 쿼리가 N개가 더 발생하는 문제

문제 설명

  • N + 1 문제는 데이터베이스에서 주로 다대일관계나 일대다 관계를 맺고 있는 엔티티를 조회할 때 발생
  • 예를 들어, 게시글과 댓글 관계가 있을 때, 게시글을 조회하고 각 게시글에 해당하는 댓글들을 추가로 조회하는 경우, 하나의 게시글에 대해 댓글을 조회할 때마다 별도의 쿼리가 실행됩니다.

N+1 문제 발생

// 게시글 목록을 조회
List<Article> articles = articleRepository.findAll();

// 각 게시글에 대해 댓글을 조회 ( 댓글을 포함하기 위해 반복문을 사용)
for(Article article : articles){
	List<Comment> comments = commentRepository.findByArticleId(article.getId());
    article.setComments(comments);
}

위 코드에서 findAll()을 통해 게시글을 조회한 후, 각 게시글마다 댓글을 조회하는 추가 쿼리가 발생합니다. 게시글이 10개이고, 각 게시글에 댓글이 5개 씩 있다면

  • 1번 쿼리 : 게시글을 조회하는 SELECT * From articles 쿼리
  • 10번 쿼리 : 각 게시글에 대한 댓글을 조회하는 SELECT * FROM comments WHERE article_id = ? 쿼리
    따라서 N+1 문제는 총 1번의 게시글 조회 쿼리와 N번의 댓글 조회 쿼리가 실행되어 총 N+1번의 쿼리가 발생하게 됩니다.

해결 방법

  1. 조인(JOIN)을 사용하여 한 번의 쿼리로 데이터를 조회
  • 게시글과 댓글의 한 번의 쿼리로 가져오기 위해 JOIN을 사용할 수 있습니다.
  • 댓글과 함께 게시글을 조회하기 위해 한 번의 JOIN 쿼리를 사용하며, 결과적으로 N+1문제를 피할 수 있습니다.
@Query"SELECT a From Article a JOIN FETCH a.comments")
List<Article> findAllWithComments();
  1. Lazy Loading과 Eager Loading
  • Lazy Loading : 기본적으로 관계를 지연 로딩으로 설정하면, 연관된 객체를 실제로 사용할 때 쿼리가 실행됩니다. 하지만 이 방식은 N+1 문제를 일으킬 수 있습니다.
  • Eager Loading : 관계를 즉시 로딩하도록 설정하면, 연관된 객체를 조회할 때 한 번의 쿼리를 모두 가져올 수 있습니다. 이를 통해 N+1 문제를 피할 수 있지만 , 필요하지 않은 데이터를 미리 로딩할 위험도 있습니다.
@OneToMany(fetch = FetchType.EAGER)
privatge List<Comment> commnets;
  1. 배치 처리
  • 배치 처리를 활용하여 여러 개의 쿼리를 한 번에 처리할 수 있도록 개선할 수 있습니다.
  • 댓글 조회를 배치로 처리하거나, 특정 조건에 맞는 데이터를 한 번에 가져오는 방식
  1. DTO를 사용하여 필요한 데이터만 가져오기
  • 필요한 데이터만 선택적으로 조회하며 불필요한 쿼리 실행을 방지할 수 있습니다.
  • 댓글의 내용만 필요하고 Article 객체만 필요하지 않다면, 필요한 속성만 포함하는 DTO를 사용하여 성능을 최적화할 수 있습니다.

0개의 댓글