[SB 3기] 코드잇 스프린트 위클리페이퍼 9주차

JHLee·2025년 6월 16일
7
post-thumbnail

Q. JPA에서 발생하는 N+1 문제의 발생 원인과 해결 방안에 대해 설명하세요.

✅ N+1 문제란?

  • JPA 또는 ORM 환경에서 특정 엔티티를 조회할 때, 연관 관계를 가진 엔티티 또한 조회하게 되면서 예상치 못한 N개의 추가 쿼리가 발생하여 성능 저하를 유발하는 문제이다.
List<Member> members = memberRepository.findAll(); // 1개의 쿼리
for (Member member : members) {
    System.out.println(member.getTeam().getName()); // N개의 추가 쿼리
} 
  • 회원 100명을 조회하고, 각 회원의 소속 부서를 출력하는 경우 :
    1(회원조회) + 100(부서조회) -> 총 101개의 쿼리가 실행

⚠️ 데이터가 많아질수록 쿼리 개수가 선형적으로 증가하기 때문에, 대규모 데이터 처리 시 심각한 성능 저하를 초래할 수 있다.

🔍 문제 발생 원인

  • 연관된 엔티티를 지연 로딩(Lazy Loading)으로 설정할 경우, 엔티티를 조회 시에는 실제 쿼리가 실행되지 않다가, 해당 연관 엔티티에 접근하는 시점(Runtime)에 추가 쿼리가 발생한다.
  • 또한, 즉시 로딩(Eager Loading)을 사용하더라도 다수의 엔티티를 컬렉션이나 연관 객체를 통해 반복적으로 접근하면, N+1 문제가 발생할 수 있다.

💡 해결 방안

1. Fetch Join 사용

  • JPQL에서 join fetch를 활용하여 연관된 엔티티를 하나의 쿼리로 함께 조회할 수 있다.
  • 연관된 엔티티도 함께 영속성 컨텍스트에 등록되므로, 추가 쿼리 없이 접근 가능하다.
@Query("SELECT m FROM Member m JOIN FETCH m.team")
List<Member> findAllWithTeam();

👍 장점

  • 가장 직관적인 방식
  • 복잡한 쿼리에도 활용 가능

👎 단점

  • 페이징 처리 불가 (메모리에서 페이징 처리가 이루어져 성능 저하)
  • 2개 이상의 컬렉션에 대한 Fetch Join은 불가능
  • 1:N 관계에서 중복된 데이터가 발생할 수 있음 -> DISTINCT 필요

2. EntityGraph 사용

  • JPA의 @EntityGraph 기능을 활용하면, JPQL 없이도 연관 엔티티를 함께 조회할 수 있다.
  • 선언적인 방식으로 간결하게 Fetch Join 효과를 낼 수 있다.
@EntityGraph(attributePaths = {"team"})
@Override
List<Member> findAll();

👍 장점

  • 간결한 코드 작성 가능
  • 페이징 처리와 함께 사용 가능

👎 단점

  • 복잡한 조인 조건이나 필터링이 필요한 쿼리에는 적합하지 않음

Q. 트랜잭션의 ACID 속성 중 격리성(Isolation)이 보장되지 않을 때 발생할 수 있는 문제점들을 설명하고, 이를 해결하기 위한 트랜잭션 격리 수준들을 설명하세요.

✅ ACID 속성이란?

데이터베이스에서 트랜잭션의 신뢰성과 일관성을 보장하기 위한 4가지 속성이다.

  • A (Atomicity, 원자성):
    트랜잭션 내의 모든 작업은 전부 수행되거나 전부 실패해야 한다.
  • C (Consistency, 일관성):
    트랜잭션 수행 전후로 데이터의 일관성이 유지되어야 한다.
  • I (Isolation, 격리성):
    여러 트랜잭션이 동시에 수행되더라도 서로 영향을 미치지 않아야 한다.
  • D (Durability, 지속성):
    커밋된 데이터는 영구적으로 저장되어야 한다.

🔍 격리성이 보장되지 않을 때 발생할 수 있는 문제점

1. Dirty Read

  • 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽는 현상
    -> 해당 트랜잭션이 롤백될 경우 잘못된 데이터를 읽게 된다.

2. Non-repeatable Read

  • 동일한 쿼리를 두 번 실행했을 때 결과가 달라지는 현상
    -> 다른 트랜잭션이 중간에 값을 변경했기 때문에 발생

3. Phantom Read

  • 같은 조건으로 쿼리를 반복 실행했을 때, 처음에는 없던 행이 나중에는 조회되는 현상
    -> 다른 트랜잭션이 중간에 데이터를 삽입하여 발생

예를 들어, 쇼핑몰에서 같은 상품의 재고가 1개일 때, 두 사용자가 동시에 주문할 경우, 격리성이 보장되지 않으면 재고 초과 판매 문제가 발생할 수 있다.

💡 트랜잭션 격리 수준

트랜잭션 격리 수준은 ANSI SQL 표준으로 4단계로 정의되며, 격리 수준이 높을수록 데이터 정합성은 보장되지만 성능은 저하될 수 있다.

격리 수준설명방지되는 현상
READ UNCOMMITTED커밋되지 않은 데이터도 읽을 수 있음❌ Dirty Read 허용
READ COMMITTED커밋된 데이터만 읽을 수 있음 (대부분의 DB 기본값)✅ Dirty Read 방지
REPEATABLE READ동일 쿼리 결과가 항상 동일함 (MySQL InnoDB 기본값)✅ Non-Repeatable Read 방지
❌ Phantom Read는 허용
SERIALIZABLE가장 높은 격리 수준, 트랜잭션 간 완전한 순차 실행 보장 (A와 B가 동시에 작업 불가)
성능 저하가 크게 발생
✅ Phantom Read까지 방지 (모든 문제 방지)

📄 참고 문서

  • 코드잇 강의 교안
profile
개발자로 성장하기

3개의 댓글

comment-user-thumbnail
2025년 6월 16일

깔끔 글 잘읽었습니당~~q(≧▽≦q)

답글 달기
comment-user-thumbnail
2025년 6월 16일

깔끔하게 잘 정리되어 있어 좋은 정보 얻어갑니다!

답글 달기
comment-user-thumbnail
2025년 6월 16일

덕분에 자세하게 알아갑니다~~!!!

답글 달기