n+1 문제

김민지·2022년 11월 17일
0

JPA

목록 보기
20/27

n+1

팀과 user의 일대다 관계가 있다.
5개의 팀과 5명의 유저가 있다
team repo에서 findAll()을 하면 하나의 쿼리만 나갈것으로 예상된다.
-> select from team;
하지만 추가적으로 team의 개수만큼의 쿼리가 더 발생한다.
왜그러는것일까?
1. findAll -> select t from Team t 라는 jpql구문이 생성되고 해당구문을 분석하여 select
from team 이라는 sql이 생성된다
2. db의 결과를 받아서 team엔티티의 인스턴스를 생성한다. 디비에 5개의 팀이 있으니 5개의 인스턴스를 생성한다
3. team과 연관되어있는 user들도 로딩을 해야한다
4. 영속성 컨텍스트에 연관된 user가 있는지 확인한다
(만약여기서 연관된 user하나만 있으면 쿼리가 하나 덜 나가게되는것일까?
아니면 똑같을까?)

5. 영속성컨텍스트에 없다면 2에서 만들어진 team인스턴스개수(5개)에 맞게
select * from user where team_id =? 라는 sql구문이 생성된다(n+1문제발생)

질문

근데.. jparepository상속받아서 만들은 인터페이스의 구현체는 findall할때
fetchjoin을 안사용하는거잖아요..? 근데 그럼 n+1문제가 발생하게돼요.
음.. 이 문제가 이렇게나 유명한데 왜 아직도 fetchjoin을 jparepository구현체에서 안사용하고 있는거예요.,...?

n+1은 해당 엔티티의 구현 상태에 따라 발생하는거고기본적인 JpaRepository가 그거를 알수는 없기 때문에보편적인 개념을 추상화해놓은거지 개발자마다 서로 다르게 구현한 엔티티까지 인식을 해서 만들어줄수는 없죠

member를 조회해올때 team을 조인해서 가져오면 되는데 왜 쿼리가 하나더 나가나요..?

  • eager니까 member와 연관관계인 team까지 필요한거고 쿼리자체가 selec m from Member m 뿐임. 그래서 쿼리가 한번 더 나간것!

아래에서 왜 ==1과 ==2사이의 쿼리가 두번나가나요?

그런데 jparepository의 findById는 연관된 객체를 즉시로딩해놨으면 left outer join 해서 가져오는듯 ? (확실x)



서로를 참조하는 테이블인 team, member가 모두 eager이기 때문에

===============1
Hibernate: 
    select
        member0_.member_id as member_i1_7_0_,
        member0_.age as age2_7_0_,
        member0_.group1_id as group6_7_0_,
        member0_.code as code3_7_0_,
        member0_.zip_code as zip_code4_7_0_,
        member0_.name as name5_7_0_,
        member0_.team_id as team_id7_7_0_,
        group1x1_.group1_id as group1_5_1_,
        group1x1_.name as name2_5_1_,
        team2_.team_id as team_id1_12_2_,
        team2_.name as name2_12_2_ 
    from
        member member0_ 
    left outer join
        group1 group1x1_ 
            on member0_.group1_id=group1x1_.group1_id 
    left outer join
        team team2_ 
            on member0_.team_id=team2_.team_id 
    where
        member0_.member_id=? //일단 member 하나를 조회하는 쿼리를 날렸고
Hibernate: 
    select
        members0_.team_id as team_id7_7_0_,
        members0_.member_id as member_i1_7_0_,
        members0_.member_id as member_i1_7_1_,
        members0_.age as age2_7_1_,
        members0_.group1_id as group6_7_1_,
        members0_.code as code3_7_1_,
        members0_.zip_code as zip_code4_7_1_,
        members0_.name as name5_7_1_,
        members0_.team_id as team_id7_7_1_,
        group1x1_.group1_id as group1_5_2_,
        group1x1_.name as name2_5_2_ 
    from
        member members0_ 
    left outer join
        group1 group1x1_ 
            on members0_.group1_id=group1x1_.group1_id 
    where
        members0_.team_id=? //member가 가진 team이 즉시로딩이라 로딩하려고하는데 team은 member를 또 즉시로딩해서 그거 처리
===============2

그리고 team을 조회하는 쿼리는 왜 안날라가나요? 즉시로딩인데

  • join을 해서 가져온다.

findAll과 findById의 차이

왜 findAll을 하게 되면 각각의 쿼리가 따로따로 나가게되고 findById를하게 되면 left outer join을쓰는거지?

https://brunch.co.kr/@springboot/595

  • findAll: jpql사용
  • findById: em.find사용

왜 쿼리가 안날라가지

요약

오늘 설명해볼 주제는 다음과 같아요
1. n+1문제

  • 팀과 user의 일대다 관계가(즉시로딩) 있다고 가정해볼게요
    2개의팀과 5명의 유저가 있어요
    team repo에서 findAll을하면 우리는 다음과 같은 쿼리가 나갈것으로 예상해요
    select + from Team;
    하지만 실제로는 team의 인스턴스의 개수인 2개만큼 쿼리가 더 발생해요
    findAll은
    select t from Team t라는 jpql구문을 생성해요.
    근데 이렇게 생성하고나서 user을 로딩해야하니까 team인스턴스별로(그 갯수만큼)
    select * from user where user.team_id=?라는 sql구문이 생성되게 돼요
  • 여기서 전 궁금한점이 있었어요 findById로 조회를 해오면 저렇게 쿼리가 따로따로 나가는게 아니라
    한번에 join을 통해서 가져오거든요
  • 여기서 findById와 findAll이 다르게 동작한다는것을알 수 있어요
    findAll: em.createQuey(select * from Team);
    findById: em.find(Team.class, ${id}); 이런식으로 다르게 동작해요.
    em.find를 하면 연관된엔티티가 즉시로딩이면 join으로 가져오나봐요!
    findall은 연관된 엔티티가 있으면 n+1문제를 발생시키는것 같구요!(확실 x 알아봐야함)
  1. 서로 eager로딩으로 해놓은 member와 team 그렇다면 쿼리는 어떻게 나갈까?
    member를 findById해놓으면 어떻게될까?
  • member를 일단조회해오는 쿼리하나
  • member와 연관된 team이 즉시로딩이라 1번쿼리에서 join을했긴했지만 team이 가지고 있는걸 로딩하던도중
    team의 member도 즉시로딩인것을알게됨 그래서 그것도 로딩하느라 쿼맇하나 더 생김

출처
https://programmer93.tistory.com/83

profile
안녕하세요!

0개의 댓글