공부/질문하는 법
Q. select b from Board b join fetch b.user u join fetch b.replies r where b.id = :id 위 쿼리 한방으로 조인 2번하는 것과 select b from Board b join fetch b.user u where b.id = :id select r from Reply r where r.board.id = :boardId 위 쿼리 처럼 2번 select 하는 것중에 성능이 머가 더 좋을까?
Q. 두 번 엑세스하면 무조건 느려? 한 번 하는 것보다?
Q. 어떤 경우에 두번의 액세스가 더 효율적일 수 있을지 예시를 알려줘
[ 해답 → 내가 컴퓨터가 되어서 테스트 해보면 됨! (¯\(°o °)/¯ ~~~?) ]
현재랑 똑같은 데이터를 만들어 직접 퍼올리며 테스트 해본다.
들어가기 전에 인덱스부터 알아야 함
프라이머리한 것들은 한 개밖에 존재하지 않는다. 데이터의 양이 적더라도 중복되어 있는게 있으면 끝까지 스캔해야 하는데 프라이머리한 것들은 찾으면 바로 끝난다는 장점이 있다. 클러스터링 (정렬) -> 집합을 시킴! 데이터가 정렬되어 있어서 들어가면 무조건 SELECT가 빠르게 이루어진다. 정렬이 되어있지 않으면 이진 트리 검색이 안 됨. 정렬이 되어 있으면서 프라이머리하면.. 속도 엄청 빠르다! 이게 바로 PK!
내가 USER에서 id 3번을 찾고 싶다. -> PK라 랜덤 엑세스 가능 BOARD에서 '제목3'을 찾고 싶다 -> 제목3이 여러개 있을 수 있으니까 랜덤 엑세스 불가능. 풀스캔 해야함 -> Reply에서 댓글3도 풀스캔 필요. '댓글3'도 여러 개가 있을 수 있으니까 Board에서 id는 PK지만, REPLY의 BOARD_ID는 FK. 즉, 여기서는 PK가 아니다. PK는 무조건 INDEX를 만들면 좋지만, FK는 이게 몇 프로정도 분포할지 생각해보고 결정. (15% 이하일 때 좋다 <- 기억나지요) 즉, Reply의 id - 랜덤 액세스 / board_id, user_id - 풀스캔을 한다.
PRIMARY 하지 않은 FK키는 다 풀스캔! 유일하지 않기 때문!
조회 2번 하는 것부터 시작
드라이빙 테이블 - > Board 드라이븐 테이블 -> User Board를 기준으로 본다! (처음엔 from 부분부터 퍼올리니까, from부터 조회)
애(Board)를 기준으로 PK를 찾으러 들어감!
id는 pk라서 찾자마자 멈춘다 -> 1번 조회 근데 user_id는 pk가 아니라서 같은 걸 찾으러 쭉 내려가며 2회 조회 (풀스캔) (정렬이 되어있어서 2회 조회) (user_id에서 pk를 찾으러 떠나는 것) 총 2회 조회
이런식으로 계속 반복해서 애네까지 (from 부분은) 총 8번 조회
이제 where 부분
where = 4를 걸어놨기 때문에 애를 찾음. 즉, 저 쿼리가 뽑은 결과는 4번 하나
Reply까지 조회해야 하니, 한 번 더!
board_id로 조회한다. 풀스캔이니 4번 조회
그럼 총 걸린 시간은 몇 번?
12번!
3개를 한 방 JOIN!
위험한 코드. 댓글이 없는 게시글이 나오지 않는다. -> left join 필요
이렇게 수정해줌 !
[ left join 시작! ]
첫 join해서 나온 이 결과를 하나의 테이블이라고 생각하고 다시 join하면 됨. -> 8번 조회 된 상태
board_id(노란색)랑 일치하는 걸 풀스캔으로 조회 (4번) 제목 1(board_id 1)은, 4번 조회(board_id 4, 4, 4, 3)했으나 Reply 를 찾지 못함. -> 5번 조회 제목 2도 똑같이 조회했으나 못찾음 -> 5번 조회
총 28번 연산
[ 결과 ]
안 좋네
[ 만약 Reply를 left로 잡았으면 어땠을까? ]
총 16번. 최소 12번 서치한다.
where 먼저 걸고 JOIN (12번짜리)
[ 이걸 사용하려면 ]
@Query("select r from Reply r right join (select b from Board b join fetch b. user u where b.id = :id") Optional<Board> hello(@Param("id") int id);
이런 식으로 네이티브 쿼리를 짜야한다…
그러나! 최고의 퍼포먼스를 내기 위한 이런 쿼리를 실무에서는 짜지 않는다.
유지 보수 힘들고, 어렵고, 불편하다… 차라리 select 2번 하는게 낫지요…
그러니 처음부터 이런 쿼리를 짜지 않고, 성능 향상이나 문제가 일어났을 때… 이런 걸 해야할 때! 사용하는 것!
결론 → LAZY 로딩을 써라
이 쿼리를 써서 LAZY 로딩 해라 → 통신 1번에 12번 조회해서 뽑아주는 것
조회 한 번 하고 뽑았죠? LAZY 로딩으로 한 번 더 뽑아오죠? 제일 좋죠?
항아리에만 담아오게 Board에 이런 필드를 생성해서 사용하면 됨!
[ JOIN 사용 한 경우 ]
* join했을 경우 최악 → 28번
* join했으나 드라이빙 테이블 변경 시 (Reply가 left면) → 16번
* where을 먼저 걸고 join (인라인뷰 사용) → 12번 (액세스(통신) 1번)
[ SELECT 사용 한 경우 ]
* select 2번 하는 건? (통신 2번) → 12번
join은 아무리 잘해도 12번인데 select 그냥 12번 하네…
근데 select는 통신을 2번 해, 근데 join은 최악이 28번이야. 으아악
이런걸 고민하면서 하라.
데이터의 구조나 데이터의 양, 이런 거에 따라서 뭘 선택할지 하면 된다.
사실 무조건 where먼저 걸고 조인 (12번)이 좋다. 근데 이게,.. JPA로 되나? 안됨!!
이런 식으로 계속 물고 늘어지면서 공부하고 실습하고 모두 검증하고 잘 하는 사람한테도 물어보고 블로그에 정리하면 된다.
Share article