Skip to content

BE 쿼리, 데이터 관리(09 27 ver)

Kim Minjoo(김민주/리브) edited this page Sep 27, 2024 · 1 revision

인덱스 현황

TABLE_NAME INDEX_NAME COLUMN_NAME SEQ_IN_INDEX NON_UNIQUE INDEX_TYPE
member PRIMARY id 1 0 BTREE
mission PRIMARY id 1 0 BTREE
discussion PRIMARY id 1 0 BTREE
discussion fk_discussion_member member_id 1 1 BTREE
discussion fk_discussion_mission mission_id 1 1 BTREE
discussion_comment PRIMARY id 1 0 BTREE
discussion_comment fk_discussion_comment_discussion discussion_id 1 1 BTREE
discussion_comment fk_discussion_comment_member member_id 1 1 BTREE
discussion_comment fk_discussion_comment_discussion_comment parent_comment_id 1 1 BTREE
discussion_hash_tag PRIMARY discussion_id 1 0 BTREE

테스트 1. 자주 사용하는 테이블의 데이터 10만건

목표

  • 대량의 데이터에서 옵티마이저의 실행 계획을 확인하고 인덱스를 걸었을 때 유의미한 차이가 있는 조회 쿼리가 있다면 인덱스를 적용합니다.
  • 데이터 적재 : 아래와 같은 이유로 운영DB로 하려 했으나 로컬DB로 변경했습니다.
    • 실제 운영 데이터 없는 점
    • 실제 운영 데이터베이스에 하면 결과 성능이 실제 성능과 동일할 것으로 예상
  • 데이터 적재량 : 각 테이블에 10만건을 먼저 적재해보고 유의미한 개선이 나오면 적용합니다. 아니면 데이터를 더 넣어서 유의미한 개선을 할 수 있을지 확인해봅니다.
  • 페이지네이션 : 아직 구현되지 않았으므로 고려하지 않았습니다.

진행 방식(목표랑 중복 제거)

  • 데이터 적재 테이블 : solution, discussion, discussion_comment, solution_comment, discussion_hash_tag
  • 테이블을 선정한 근거 :
    • 사용자가 직접 적재할 수 있는 테이블
    • 사용자가 많을 수록 더 많은 조회가 이루어질 것이라고 판단되는 테이블
  • 각 데이터는 이런 기준으로 생성했습니다.
    • discussion
      • discussion 은 총 99001 행입니다.
      • discussion.mission_id 는 1~6, member_id 는 1~3 을 랜덤으로 조합하여 생성했습니다.
      • discussion.create_at은 1분 간격으로 생성했습니다.
    • discussion_comment
      • discussion_comment 은 총 100000 행입니다.
      • discussion_comment.discussion_id 는 1~99001, member_id 는 1~3 을 랜덤으로 조합하여 생성했습니다.
      • discussion_comment.parent_comment_id 는 null 값과 1~10000 의 값을 랜덤으로 사용했습니다.
      • discussion_comment.deleted_at 은 모두 null 입니다.
      • discussion_comment.create_at은 1분 간격으로 생성했습니다.
    • discussion_hash_tag
      • discussion_hash_tag 은 총 99294 행입니다.
      • discussion_hash_tag.discussion_id 는 1~99000, hash_tag_id 는 1~5 을 랜덤으로 조합하여 생성했습니다.
    • solution
      • solution 은 총 100000 행입니다.
      • solution.mission_id 는 1~6, member_id 는 1~3 을 랜덤으로 조합하여 생성했습니다.
      • solution.status 는 ‘COMPLETED’, ‘IN_PROGRESS’ 의 두 값을 랜덤으로 사용했습니다.
      • solution.create_at은 1분 간격으로 생성했습니다.
    • solution_comment
      • solution_comment 은 총 100000 행입니다.
      • solution_comment.solution_id 는 1~32768, member_id 는 1~3 을 랜덤으로 조합하여 생성했습니다.
      • solution_comment.parent_comment_id 는 null 값과 1~10000 의 값을 랜덤으로 사용했습니다.
      • solution_comment.deleted_at 은 모두 null 입니다.
      • solution_comment.create_at은 1분 간격으로 생성했습니다.
  • 테스트는 로컬 DB에서 진행했습니다.
    • 근거 :
      • 운영 DB를 사용하면 운영 WAS에서 페이지네이션을 처리안하니까 OOM 발생
      • 개발 DB를 사용하면 좋은 성능 발휘할 수 없고, 데이터 적재 속도가 느리고, 현재 개발 DB를 프론트엔드에서 사용중
  • 테스트 데이터는 스크립트로 적재했습니다.
  • swagger를 보면서 적재한 테이블과 연관된 api 목록을 추출했습니다.
  • api 를 호출하면서 발생하는 쿼리들을 수집했습니다.
  • 쿼리들의 실행 계획을 모았습니다.

적용 및 결과

  • 대부분의 쿼리들이 fk와 pk를 이용해서 인덱스를 사용하기 때문에 인덱스를 생성하지 않았습니다.
  • 몇몇 쿼리들(필터링 관련 쿼리)은 해시태그를 조회하는 서브쿼리**(다른 곳도 진짜 풀 테이블 스캔인가?)**에서 풀 테이블 스캔이 발생합니다. → 추가 검증 필요

인덱스를 생성하지 않은 부분

  • 필터링 관련 쿼리들은 해시태그를 조회하는 서브쿼리에서 테이블 스캔이 발생했습니다.
  • 해시태그 이름에 인덱스를 생성하면 커버링 인덱스를 사용할 수 있습니다.
  • 하지만, 행 개수가 적은 테이블에서는 Full Table Scan을 이용하는 게 나을 수 있기 때문에 적용하지 않았습니다. 실제로도 유의미한 성능 향상을 확인하지 못했습니다. (인덱스를 사용하는 경우 오히려 성능이 떨어지거나 오차범위 이내의 차이였습니다.)
  • 유니크 인덱스를 생성해도 커버링 인덱스를 사용할지 궁금하여 유니크 제약 조건을 추가했습니다.
  • 유니크 제약 조건을 설정하면 조회 속도가 약 18%정도 향상된 것을 확인할 수 있었습니다.

image.png

테스트 2. 인덱스 제약 조건이 왜 빨랐는가?

인덱스 제약 조건 성능 가설

  • 해시 조인이라고 생각하였으나..

데이터를 추가해보기 - 1

근거 : 유니크 제약조건 성능 향상이 드라마틱한지 확신을 얻기 위해 더 많은 데이터를 적재해 실험했습니다.

추가 데이터 : 해시 태그 100개, 미션 100개

결과 : 유니크 인덱스 vs hash tag name 인덱스, 인덱스 없음 모두 동일한 성능

  • 유니크 인덱스

    image.png

    -> Sort: s1_0.id DESC, ht1_0.id  (actual time=12073.248..12394.620 rows=125673 loops=1)
        -> Table scan on <temporary>  (cost=0.01..117.72 rows=9219) (actual time=0.197..517.966 rows=125673 loops=1)
            -> Temporary table with deduplication  (cost=98729.09..98846.80 rows=9219) (actual time=10743.114..11311.378 rows=125673 loops=1)
                -> Remove duplicate (m1_0, ht1_0, s1_0) rows using temporary table (weedout)  (cost=97807.19 rows=9219) (actual time=3.284..3278.439 rows=125673 loops=1)
                    -> Inner hash join (s1_0.mission_id = mht1_0.mission_id)  (cost=97807.19 rows=9219) (actual time=3.183..1009.431 rows=125673 loops=1)
                        -> Filter: (s1_0.`status` = 'COMPLETED')  (cost=221.83 rows=1980) (actual time=0.236..700.179 rows=50218 loops=1)
                            -> Table scan on s1_0  (cost=221.83 rows=99000) (actual time=0.193..566.615 rows=100000 loops=1)
                        -> Hash
                            -> Nested loop inner join  (cost=267.99 rows=233) (actual time=1.721..2.628 rows=28 loops=1)
                                -> Inner hash join (ht1_0.mission_id = mht1_0.mission_id)  (cost=237.43 rows=233) (actual time=1.618..2.308 rows=28 loops=1)
                                    -> Table scan on ht1_0  (cost=0.40 rows=291) (actual time=0.166..0.590 rows=291 loops=1)
                                    -> Hash
                                        -> Nested loop inner join  (cost=4.35 rows=8) (actual time=0.858..1.090 rows=8 loops=1)
                                            -> Index lookup on mht1_0 using fk_mission_hash_tag_hash_tag (hash_tag_id='2')  (cost=1.55 rows=8) (actual time=0.665..0.724 rows=8 loops=1)
                                            -> Single-row index lookup on m1_0 using PRIMARY (id=mht1_0.mission_id)  (cost=0.26 rows=1) (actual time=0.039..0.039 rows=1 loops=8)
                                -> Single-row index lookup on ht2_0 using PRIMARY (id=ht1_0.hash_tag_id)  (cost=0.03 rows=1) (actual time=0.009..0.010 rows=1 loops=28)
    
  • hash tag name 인덱스

    image.png

    -> Sort: s1_0.id DESC, ht1_0.id  (actual time=11303.050..11597.919 rows=125673 loops=1)
        -> Table scan on <temporary>  (cost=0.01..2163.18 rows=172854) (actual time=0.263..480.797 rows=125673 loops=1)
            -> Temporary table with deduplication  (cost=216063.83..218226.99 rows=172854) (actual time=10095.696..10621.958 rows=125673 loops=1)
                -> Nested loop inner join  (cost=198778.41 rows=172854) (actual time=1305.129..2336.105 rows=125673 loops=1)
                    -> Inner hash join (ht1_0.mission_id = `<subquery2>`.mission_id)  (cost=181485.74 rows=172854) (actual time=1304.898..1937.772 rows=125673 loops=1)
                        -> Table scan on ht1_0  (cost=0.01 rows=291) (actual time=0.638..1.153 rows=291 loops=1)
                        -> Hash
                            -> Nested loop inner join  (cost=8615.54 rows=5940) (actual time=1.502..981.931 rows=41891 loops=1)
                                -> Nested loop inner join  (cost=4.79 rows=3) (actual time=1.220..18.252 rows=8 loops=1)
                                    -> Table scan on <subquery2>  (cost=0.85..2.54 rows=3) (actual time=0.021..0.133 rows=8 loops=1)
                                        -> Materialize with deduplication  (cost=2.55..4.24 rows=3) (actual time=1.111..1.269 rows=8 loops=1)
                                            -> Nested loop inner join  (cost=1.40 rows=3) (actual time=0.844..0.950 rows=8 loops=1)
                                                -> Covering index lookup on ht3_0 using idx_hash_tag_name (name='객체지향')  (cost=0.35 rows=1) (actual time=0.395..0.405 rows=1 loops=1)
                                                -> Index lookup on mht1_0 using fk_mission_hash_tag_hash_tag (hash_tag_id=ht3_0.id)  (cost=1.05 rows=3) (actual time=0.425..0.514 rows=8 loops=1)
                                    -> Single-row index lookup on m1_0 using PRIMARY (id=`<subquery2>`.mission_id)  (cost=0.12 rows=1) (actual time=2.095..2.097 rows=1 loops=8)
                                -> Filter: (s1_0.`status` = 'COMPLETED')  (cost=2868.75 rows=1980) (actual time=0.638..116.882 rows=5236 loops=8)
                                    -> Index lookup on s1_0 using fk_solution_mission (mission_id=`<subquery2>`.mission_id)  (cost=2868.75 rows=19800) (actual time=0.527..104.063 rows=10424 loops=8)
                    -> Single-row index lookup on ht2_0 using PRIMARY (id=ht1_0.hash_tag_id)  (cost=0.00 rows=1) (actual time=0.001..0.002 rows=1 loops=125673)
    
  • 인덱스 없음

    image.png

    -> Sort: s1_0.id DESC, ht1_0.id  (actual time=11488.641..11790.120 rows=125673 loops=1)
        -> Table scan on <temporary>  (cost=0.01..477.84 rows=38028) (actual time=0.234..494.489 rows=125673 loops=1)
            -> Temporary table with deduplication  (cost=63839.05..64316.88 rows=38028) (actual time=10148.223..10690.227 rows=125673 loops=1)
                -> Nested loop inner join  (cost=60036.25 rows=38028) (actual time=1151.747..2151.108 rows=125673 loops=1)
                    -> Inner hash join (ht1_0.mission_id = `<subquery2>`.mission_id)  (cost=56232.74 rows=38028) (actual time=1151.398..1758.675 rows=125673 loops=1)
                        -> Table scan on ht1_0  (cost=0.01 rows=291) (actual time=0.323..0.971 rows=291 loops=1)
                        -> Hash
                            -> Inner hash join (s1_0.mission_id = `<subquery2>`.mission_id)  (cost=18188.65 rows=1307) (actual time=2.691..896.256 rows=41891 loops=1)
                                -> Filter: (s1_0.`status` = 'COMPLETED')  (cost=11820.09 rows=1980) (actual time=0.157..776.015 rows=50218 loops=1)
                                    -> Table scan on s1_0  (cost=11820.09 rows=99000) (actual time=0.148..627.283 rows=100000 loops=1)
                                -> Hash
                                    -> Nested loop inner join  (cost=32.56 rows=33) (actual time=2.049..2.200 rows=8 loops=1)
                                        -> Table scan on <subquery2>  (cost=0.09..2.91 rows=33) (actual time=0.007..0.012 rows=8 loops=1)
                                            -> Materialize with deduplication  (cost=26.19..29.01 rows=33) (actual time=1.934..1.943 rows=8 loops=1)
                                                -> Nested loop inner join  (cost=22.80 rows=33) (actual time=1.452..1.784 rows=8 loops=1)
                                                    -> Filter: (ht3_0.`name` = '객체지향')  (cost=11.25 rows=11) (actual time=0.913..1.172 rows=1 loops=1)
                                                        -> Table scan on ht3_0  (cost=11.25 rows=110) (actual time=0.782..0.992 rows=110 loops=1)
                                                    -> Index lookup on mht1_0 using fk_mission_hash_tag_hash_tag (hash_tag_id=ht3_0.id)  (cost=0.78 rows=3) (actual time=0.474..0.541 rows=8 loops=1)
                                        -> Single-row index lookup on m1_0 using PRIMARY (id=`<subquery2>`.mission_id)  (cost=0.01 rows=1) (actual time=0.030..0.030 rows=1 loops=8)
                    -> Single-row index lookup on ht2_0 using PRIMARY (id=ht1_0.hash_tag_id)  (cost=0.00 rows=1) (actual time=0.001..0.002 rows=1 loops=125673)
    

데이터를 추가해보기 - 2

근거 : 해시 태그, 미션 데이터가 16배 가량 증가했으니, 솔루션 데이터도 160만개 추가했다.

추가 데이터 : 솔루션 170만개 추가

결과 : 유니크 인덱스 vs hash tag name 인덱스, 인덱스 없음 모두 동일한 성능

데이터를 추가해보기 - 3

근거 : 앞선 실험의 결과가 어떤 데이터의 변화에 의한 것인지 판단한다.

추가 데이터 : 솔루션 40만개

결과 : 유니크 인덱스 vs hash tag name 인덱스, 인덱스 없음 모두 동일한 성능

image.png

결론. 유니크 인덱스 사용하지 않는다.

근거 :

  • 데이터가 평균 10만건인 상황에서 조금 더 나은 성능을 보였지만, 그 이상의 환경에서는 유니크 인덱스가 성능이 안좋았다가, 또 그 이상의 상황에서는 동등해지기도 한다. 불확실성이 강하다.
  • 인덱스를 생성하기 보다 쿼리 튜닝부터 선행되어야 한다.

솔루션 목록 조회 ✓

    select
        distinct s1_0.id,
        s1_0.created_at,
        s1_0.description,
        s1_0.member_id,
        s1_0.mission_id,
        m1_0.id,
        ht1_0.mission_id,
        ht1_0.id,
        ht1_0.hash_tag_id,
        ht2_0.id,
        ht2_0.name,
        m1_0.url,
        m1_0.summary,
        m1_0.thumbnail,
        m1_0.title,
        s1_0.url,
        s1_0.status,
        s1_0.title 
    from
        solution s1_0 
    join
        mission m1_0 
            on m1_0.id=s1_0.mission_id 
    join
        mission_hash_tag ht1_0 
            on m1_0.id=ht1_0.mission_id 
    join
        hash_tag ht2_0 
            on ht2_0.id=ht1_0.hash_tag_id
    where
        s1_0.status='COMPLETED' 
        and exists(select
            1 
        from
            mission_hash_tag mht1_0 
        join
            hash_tag ht3_0 
                on ht3_0.id=mht1_0.hash_tag_id 
        where
            mht1_0.mission_id=s1_0.mission_id 
            and (lower(?)='all' 
            or ht3_0.name=?)) 
    order by
        s1_0.id desc,
        ht1_0.id

BEFORE 실행 계획

image.png

  1. 실행계획 1 : 서브쿼리 풀 테이블 스캔

    1. Using Temporary : 쿼리 결과를 생성하기 위해 MySQL이 임시 테이블 사용 ➡️ 메모리에 생성되는데 데이터가 많으면 디스크에 생성될 수도 있음 ➡️ 성능 저하 가능성 있음

    2. Using Filesort : ORDER BY 절이 사용 ➕ 인덱스를 사용하여 정렬할 수 없음 ➡️ 데이터의 양이 메모리에 비해 많으면 디스크를 사용하여 정렬 ➡️ 성능 저하 가능성 있음

      1. 솔루션 내림차순 정렬 고려 :
      CREATE INDEX index_solution_desc ON solution (id DESC);
      
      1. 복합 인덱스 적용 고려 : CREATE INDEX idx_mission_hash_tag_id ON mission_hash_tag(mission_id, id);
  2. 실행계획 2 : m1_0(mission) 프라이머리키 인덱싱

  3. 실행계획 3 : fk_solution_mission 외래키 인덱싱

  4. 실행계획 4 : fk_mission_hash_tag_hash_tag(hash_tag) 외래키 인덱싱

  5. 실행계획 5 : ht2_0(hash_tag) : 프라이머리키 인덱싱

  6. 실행계획 6 : MATERIALIZED 서브쿼리 / where 절 풀 테이블 스캔

            where
                mht1_0.mission_id=s1_0.mission_id 
                and (lower(?)='all' 
                or ht3_0.name=?)) 
    
  7. 실행계획 7 : MATERIALIZED 서브쿼리 / fk_mission_hash_tag_hash_tag(hash_tag) 외래키 인덱싱

    MATERIALIZED 서브쿼리도 메모리 소모 가능성 있음

    1. 복합 인덱스 적용 고려 : CREATE INDEX idx_mission_hash_tag_combined ON mission_hash_tag(mission_id, hash_tag_id); ➡️ WHERE 및 JOIN 최적화

<aside>

MATERIALIZED 서브쿼리

  • 임시 테이블 생성: 서브쿼리의 결과가 메모리나 디스크 상의 임시 테이블에 저장됩니다. 이후 이 테이블이 여러 번 사용될 수 있습니다.
  • 성능 최적화: 동일한 서브쿼리를 여러 번 실행하지 않기 때문에 중복 작업을 줄이고 성능을 향상시킵니다.
  • 중복 실행 방지: MATERIALIZED는 중복 계산을 방지하여 쿼리를 최적화하려는 의도로 사용됩니다. </aside>
explain analyze
select
    distinct s1_0.id,
             s1_0.created_at,
             s1_0.description,
             s1_0.member_id,
             s1_0.mission_id,
             m1_0.id,
             ht1_0.mission_id,
             ht1_0.id,
             ht1_0.hash_tag_id,
             ht2_0.id,
             ht2_0.name,
             m1_0.url,
             m1_0.summary,
             m1_0.thumbnail,
             m1_0.title,
             s1_0.url,
             s1_0.status,
             s1_0.title
from
    solution s1_0
        join
    mission m1_0
    on m1_0.id=s1_0.mission_id
        join
    mission_hash_tag ht1_0
    on m1_0.id=ht1_0.mission_id
        join
    hash_tag ht2_0
    on ht2_0.id=ht1_0.hash_tag_id
where
    s1_0.status='COMPLETED'
  and exists(select
                 1
             from
                 mission_hash_tag mht1_0
                     join
                 hash_tag ht3_0
                 on ht3_0.id=mht1_0.hash_tag_id
             where
                 mht1_0.mission_id=s1_0.mission_id
               and (lower('객체지향')='all'
                 or ht3_0.name='객체지향'))
order by
    s1_0.id desc,
    ht1_0.id;

솔루션 단 건 조회 - 릴리 ✓

select
        distinct s1_0.id,
        s1_0.created_at,
        s1_0.description,
        s1_0.member_id,
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id,
        s1_0.mission_id,
        m2_0.id,
        ht1_0.mission_id,
        ht1_0.id,
        ht1_0.hash_tag_id,
        ht2_0.id,
        ht2_0.name,
        m2_0.url,
        m2_0.summary,
        m2_0.thumbnail,
        m2_0.title,
        s1_0.url,
        s1_0.status,
        s1_0.title 
    from
        solution s1_0 
    join
        member m1_0 
            on m1_0.id=s1_0.member_id 
    join
        mission m2_0 
            on m2_0.id=s1_0.mission_id 
    join
        mission_hash_tag ht1_0 
            on m2_0.id=ht1_0.mission_id 
    join
        hash_tag ht2_0 
            on ht2_0.id=ht1_0.hash_tag_id 
    where
        s1_0.id=? 
    order by
        ht1_0.id

BEFORE 실행계획

image.png

  • 실행 계획 1 : solution / const(primary) / 기본 키 또는 고유키에 의한 조회
  • 실행 계획 2 : member / const(primary) / 기본 키 또는 고유키에 의한 조회
  • 실행 계획 3 : mission / const(primary) / 기본 키 또는 고유키에 의한 조회
  • 실행 계획 4 : mission_hash_tag / ref(fk_mission_hash_tag_mission) / 고유 키가 아닌 인덱스
  • 실행 계획 5 : hash_tag / eq_ref(primary) / 해시 태그 테이블의 기본 키에 의한 조회, 외래키와 기본 키 간의 정확한 1:1 대응일 때 사용

디스커션 목록 조회

select
        d1_0.id,
        d1_0.content,
        d1_0.created_at,
        ht1_0.discussion_id,
        ht1_0.hash_tag_id,
        ht2_0.id,
        ht2_0.name,
        d1_0.member_id,
        m1_0.id,
        m1_0.url,
        m1_0.summary,
        m1_0.thumbnail,
        m1_0.title,
        d1_0.title 
    from
        discussion d1_0 
    join
        mission m1_0 
            on m1_0.id=d1_0.mission_id 
    join
        discussion_hash_tag ht1_0 
            on d1_0.id=ht1_0.discussion_id 
    join
        hash_tag ht2_0 
            on ht2_0.id=ht1_0.hash_tag_id 
    where
        (
            lower(?)='all' 
            or m1_0.title=?
        ) 
        and (
            lower(?)='all' 
            or exists(select
                1 
            from
                discussion_hash_tag dht1_0 
            join
                hash_tag ht3_0 
                    on ht3_0.id=dht1_0.hash_tag_id 
            where
                dht1_0.discussion_id=d1_0.id 
                and ht3_0.name=?)
        ) 
    order by
        ht1_0.discussion_id,
        ht1_0.hash_tag_id

select m1_0.id, m1_0.created_at, m1_0.email, m1_0.image_url, m1_0.name, m1_0.provider, m1_0.social_id from member m1_0 where m1_0.id=?

select m1_0.id, m1_0.created_at, m1_0.email, m1_0.image_url, m1_0.name, m1_0.provider, m1_0.social_id from member m1_0 where m1_0.id=?

BEFORE 실행 계획

image.png

image.png

디스커션 단 건 조회

select
        d1_0.id,
        d1_0.content,
        d1_0.created_at,
        ht2_0.discussion_id,
        ht2_0.hash_tag_id,
        ht3_0.id,
        ht3_0.name,
        d1_0.member_id,
        m2_0.id,
        m2_0.created_at,
        m2_0.email,
        m2_0.image_url,
        m2_0.name,
        m2_0.provider,
        m2_0.social_id,
        m1_0.id,
        ht1_0.mission_id,
        ht1_0.id,
        ht1_0.hash_tag_id,
        m1_0.url,
        m1_0.summary,
        m1_0.thumbnail,
        m1_0.title,
        d1_0.title 
    from
        discussion d1_0 
    join
        mission m1_0 
            on m1_0.id=d1_0.mission_id 
    join
        mission_hash_tag ht1_0 
            on m1_0.id=ht1_0.mission_id 
    join
        member m2_0 
            on m2_0.id=d1_0.member_id 
    join
        discussion_hash_tag ht2_0 
            on d1_0.id=ht2_0.discussion_id 
    join
        hash_tag ht3_0 
            on ht3_0.id=ht2_0.hash_tag_id 
    where
        d1_0.id=? 
    order by
        ht2_0.discussion_id,
        ht2_0.hash_tag_id,
        ht1_0.id

BEFORE 실행 계획

image.png

솔루션 댓글 조회 - 릴리 ✓

select
        sc1_0.id,
        sc1_0.content,
        sc1_0.created_at,
        sc1_0.deleted_at,
        sc1_0.member_id,
        sc1_0.parent_comment_id,
        sc1_0.solution_id 
    from
        solution_comment sc1_0 
    where
        sc1_0.solution_id=? 
    order by
        sc1_0.created_at

select m1_0.id, m1_0.created_at, m1_0.email, m1_0.image_url, m1_0.name, m1_0.provider, m1_0.social_id from member m1_0 where m1_0.id=?

BEFORE 실행 계획

image.png

image.png

  • 실행 계획 1 : solution_comment / ref(fk_solution_comment_solution) / 고유 키가 아닌 인덱스
  • 실행 계획 2 : member / const(primary) / 기본 키 또는 고유키에 의한 조회

디스커션 댓글 목록 조회 - 구름

/discussions/{discussionId}/comments

select
        dc1_0.id,
        dc1_0.content,
        dc1_0.created_at,
        dc1_0.deleted_at,
        dc1_0.discussion_id,
        dc1_0.member_id,
        dc1_0.parent_comment_id 
    from
        discussion_comment dc1_0 
    where
        dc1_0.discussion_id=? 
    order by
        dc1_0.created_at
2024-09-25 17:30:12.694 | http-nio-8080-exec-1 | traceId=b00d91b7-8a48-4981-ab69-94f3e723be4b | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
[Hibernate] 
    select
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id 
    from
        member m1_0 
    where
        m1_0.id=?

BEFORE 실행 계획

image.png

image.png

  • 실행 계획 1 : discussion_comment / ref(fk_discussion_comment_discussion) / 고유 키가 아닌 인덱스
  • 실행 계획 2 : member / const(primary) / 기본 키 또는 고유키에 의한 조회

디스커션 댓글 추가 - 리브 ✓

/discussions/{discussionId}/comments

select
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id 
    from
        member m1_0 
    where
        m1_0.id=?
2024-09-25 17:39:46.442 | http-nio-8080-exec-2 | traceId=fc40d324-52cf-4252-8c1c-aea996c3ff1f | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
2024-09-25 17:39:46.443 | http-nio-8080-exec-2 | traceId=fc40d324-52cf-4252-8c1c-aea996c3ff1f | DEBUG | o.s.orm.jpa.JpaTransactionManager | Found thread-bound EntityManager [SessionImpl(274023016<open>)] for JPA transaction
2024-09-25 17:39:46.443 | http-nio-8080-exec-2 | traceId=fc40d324-52cf-4252-8c1c-aea996c3ff1f | DEBUG | o.s.orm.jpa.JpaTransactionManager | Participating in existing transaction
[Hibernate] 
    select
        d1_0.id,
        d1_0.content,
        d1_0.created_at,
        d1_0.member_id,
        d1_0.mission_id,
        d1_0.title 
    from
        discussion d1_0 
    where
        d1_0.id=?
2024-09-25 17:39:46.444 | http-nio-8080-exec-2 | traceId=fc40d324-52cf-4252-8c1c-aea996c3ff1f | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
2024-09-25 17:39:46.446 | http-nio-8080-exec-2 | traceId=fc40d324-52cf-4252-8c1c-aea996c3ff1f | DEBUG | o.s.orm.jpa.JpaTransactionManager | Found thread-bound EntityManager [SessionImpl(274023016<open>)] for JPA transaction
2024-09-25 17:39:46.446 | http-nio-8080-exec-2 | traceId=fc40d324-52cf-4252-8c1c-aea996c3ff1f | DEBUG | o.s.orm.jpa.JpaTransactionManager | Participating in existing transaction
[Hibernate] 
    insert 
    into
        discussion_comment
        (content, created_at, deleted_at, discussion_id, member_id, parent_comment_id, id) 
    values
        (?, ?, ?, ?, ?, ?, default)

BEFORE 실행 계획

image.png

image.png

image.png

select
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id 
    from
        member m1_0 
    where
        m1_0.id=?
  • 1번 쿼리(멤버 조회): const(프라이머리 키) 조회
    select
        d1_0.id,
        d1_0.content,
        d1_0.created_at,
        d1_0.member_id,
        d1_0.mission_id,
        d1_0.title 
    from
        discussion d1_0 
    where
        d1_0.id=?
  • 2번 쿼리(디스커션 조회) : const(프라이머리 키) 조회
    insert 
    into
        discussion_comment
        (content, created_at, deleted_at, discussion_id, member_id, parent_comment_id, id) 
    values
        (?, ?, ?, ?, ?, ?, default)
  • 3번 뭐리(디스커션 댓글 추가) : ALL

디스커션 댓글 삭제

 select
        dc1_0.id,
        dc1_0.content,
        dc1_0.created_at,
        dc1_0.deleted_at,
        dc1_0.discussion_id,
        dc1_0.member_id,
        dc1_0.parent_comment_id 
    from
        discussion_comment dc1_0 
    where
        dc1_0.id=?
2024-09-25 17:40:49.757 | http-nio-8080-exec-4 | traceId=3cdb2753-1d08-42f7-9ba0-371a57766fe5 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [9]
2024-09-25 17:40:49.761 | http-nio-8080-exec-4 | traceId=3cdb2753-1d08-42f7-9ba0-371a57766fe5 | DEBUG | o.s.orm.jpa.JpaTransactionManager | Initiating transaction commit
2024-09-25 17:40:49.761 | http-nio-8080-exec-4 | traceId=3cdb2753-1d08-42f7-9ba0-371a57766fe5 | DEBUG | o.s.orm.jpa.JpaTransactionManager | Committing JPA transaction on EntityManager [SessionImpl(2044199012<open>)]
[Hibernate] 
    update
        discussion_comment 
    set
        content=?,
        deleted_at=?,
        discussion_id=?,
        member_id=?,
        parent_comment_id=? 
    where
        id=?

BEFORE 실행계획

image.png

image.png

디스커션 댓글 수정 - 리브 ✓

select
        dc1_0.id,
        dc1_0.content,
        dc1_0.created_at,
        dc1_0.deleted_at,
        dc1_0.discussion_id,
        dc1_0.member_id,
        dc1_0.parent_comment_id 
    from
        discussion_comment dc1_0 
    where
        dc1_0.id=?
2024-09-25 17:41:32.018 | http-nio-8080-exec-5 | traceId=540ec63f-56ff-4309-9c84-7948a794a070 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [8]
[Hibernate] 
    select
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id 
    from
        member m1_0 
    where
        m1_0.id=?
2024-09-25 17:41:32.020 | http-nio-8080-exec-5 | traceId=540ec63f-56ff-4309-9c84-7948a794a070 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
2024-09-25 17:41:32.022 | http-nio-8080-exec-5 | traceId=540ec63f-56ff-4309-9c84-7948a794a070 | DEBUG | o.s.orm.jpa.JpaTransactionManager | Initiating transaction commit
2024-09-25 17:41:32.022 | http-nio-8080-exec-5 | traceId=540ec63f-56ff-4309-9c84-7948a794a070 | DEBUG | o.s.orm.jpa.JpaTransactionManager | Committing JPA transaction on EntityManager [SessionImpl(2010075036<open>)]
[Hibernate] 
    update
        discussion_comment 
    set
        content=?,
        deleted_at=?,
        discussion_id=?,
        member_id=?,
        parent_comment_id=? 
    where
        id=?

BEFORE 실행 계획

image.png

image.png

image.png

select
        dc1_0.id,
        dc1_0.content,
        dc1_0.created_at,
        dc1_0.deleted_at,
        dc1_0.discussion_id,
        dc1_0.member_id,
        dc1_0.parent_comment_id 
    from
        discussion_comment dc1_0 
    where
        dc1_0.id=?
  • 1번 쿼리(디스커션 댓글 조회): const(프라이머리 키) 조회
    select
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id 
    from
        member m1_0 
    where
        m1_0.id=?
  • 2번 쿼리(멤버 조회): const(프라이머리 키) 조회
    update
        discussion_comment 
    set
        content=?,
        deleted_at=?,
        discussion_id=?,
        member_id=?,
        parent_comment_id=? 
    where
        id=?
  • 3번 쿼리(디스커션 댓글 수정) : range(프라이머리 키 특정 범위 행만 검색)
    • 프라이머리 키라서 const가 나와야할 것 같은데 왜 range 조회하는지?
      • 다른 테이블에서 외래키로 사용되어서일 가능성이 가장 높다.

사용자가 작성한 디스커션에 단 댓글 목록 조회

select
        dc1_0.id,
        dc1_0.discussion_id,
        dc1_0.content,
        dc1_0.created_at,
        d1_0.title,
        count(dc1_0.id) 
    from
        discussion_comment dc1_0 
    join
        discussion d1_0 
            on d1_0.id=dc1_0.discussion_id 
    where
        dc1_0.member_id=? 
        and dc1_0.deleted_at is null 
    group by
        dc1_0.id

실행 계획

image.png

  • 실행 계획 1 : discussion_comment / ref(fk_discussion_comment_member) / 고유 키가 아닌 인덱스
  • 실행 계획 2 : discussion / eq_ref(primary) / 조인에서 처음 읽은 테이블의 칼럼값을, 다음 읽을 테이블의 PK나 유니크 키 칼럼의 검색 조건에 사용

나의 디스커션 조회 ✓

select
        d1_0.id,
        d1_0.content,
        d1_0.created_at,
        ht1_0.discussion_id,
        ht1_0.hash_tag_id,
        ht2_0.id,
        ht2_0.name,
        d1_0.member_id,
        m2_0.id,
        m2_0.created_at,
        m2_0.email,
        m2_0.image_url,
        m2_0.name,
        m2_0.provider,
        m2_0.social_id,
        m1_0.id,
        m1_0.url,
        m1_0.summary,
        m1_0.thumbnail,
        m1_0.title,
        d1_0.title 
    from
        discussion d1_0 
    join
        mission m1_0 
            on m1_0.id=d1_0.mission_id 
    join
        member m2_0 
            on m2_0.id=d1_0.member_id 
    join
        discussion_hash_tag ht1_0 
            on d1_0.id=ht1_0.discussion_id 
    join
        hash_tag ht2_0 
            on ht2_0.id=ht1_0.hash_tag_id 
    where
        d1_0.member_id=? 
    order by
        ht1_0.discussion_id,
        ht1_0.hash_tag_id

실행 계획

image.png

분석

  • 실행 계획 1 : member / const(primary) / 기본 키 또는 고유키에 의한 조회
  • 실행 계획 2 : mission / all / rows가 낮아서 그런가?
  • 실행 계획 3 : discussion / ref(fk_discussion_mission) / 고유 키가 아닌 인덱스
  • 실행 계획 4 : mission_hash_tag / ref(primary) / 고유 키가 아닌 인덱스, using index(커버링 인덱스)
  • 실행 계획 5 : hash_tag / eq_ref(primary) / 조인에서 처음 읽은 테이블의 칼럼값을, 다음 읽을 테이블의 PK나 유니크 키 칼럼의 검색 조건에 사용

디스커션 제출 ✓

select
        m1_0.id,
        m1_0.url,
        m1_0.summary,
        m1_0.thumbnail,
        m1_0.title 
    from
        mission m1_0 
    where
        m1_0.id=?
2024-09-25 17:45:03.778 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
2024-09-25 17:45:03.780 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | DEBUG | o.s.orm.jpa.JpaTransactionManager | Found thread-bound EntityManager [SessionImpl(1067221385<open>)] for JPA transaction
2024-09-25 17:45:03.780 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | DEBUG | o.s.orm.jpa.JpaTransactionManager | Participating in existing transaction
[Hibernate] 
    select
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id 
    from
        member m1_0 
    where
        m1_0.id=?
2024-09-25 17:45:03.780 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
[Hibernate] 
    select
        ht1_0.id,
        ht1_0.name 
    from
        hash_tag ht1_0 
    where
        ht1_0.id in (?)
2024-09-25 17:45:03.784 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
2024-09-25 17:45:03.785 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | DEBUG | o.s.orm.jpa.JpaTransactionManager | Found thread-bound EntityManager [SessionImpl(1067221385<open>)] for JPA transaction
2024-09-25 17:45:03.785 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | DEBUG | o.s.orm.jpa.JpaTransactionManager | Participating in existing transaction
[Hibernate] 
    insert 
    into
        discussion
        (content, created_at, member_id, mission_id, title, id) 
    values
        (?, ?, ?, ?, ?, default)
2024-09-25 17:45:03.787 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:VARCHAR) <- [string]
2024-09-25 17:45:03.787 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (2:TIMESTAMP) <- [2024-09-25T17:45:03.786059]
2024-09-25 17:45:03.787 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (3:BIGINT) <- [1]
2024-09-25 17:45:03.787 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (4:BIGINT) <- [1]
2024-09-25 17:45:03.787 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (5:VARCHAR) <- [string]
[Hibernate] 
    select
        ht1_0.mission_id,
        ht1_0.id,
        ht1_0.hash_tag_id 
    from
        mission_hash_tag ht1_0 
    where
        ht1_0.mission_id=? 
    order by
        ht1_0.id
2024-09-25 17:45:03.788 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
[Hibernate] 
    select
        ht1_0.id,
        ht1_0.name 
    from
        hash_tag ht1_0 
    where
        ht1_0.id=?
2024-09-25 17:45:03.790 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [2]
[Hibernate] 
    select
        ht1_0.id,
        ht1_0.name 
    from
        hash_tag ht1_0 
    where
        ht1_0.id=?
2024-09-25 17:45:03.790 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [3]
2024-09-25 17:45:03.790 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | DEBUG | o.s.orm.jpa.JpaTransactionManager | Initiating transaction commit
2024-09-25 17:45:03.790 | http-nio-8080-exec-2 | traceId=538f4406-1fbe-4d63-b526-50c8b3db31ab | DEBUG | o.s.orm.jpa.JpaTransactionManager | Committing JPA transaction on EntityManager [SessionImpl(1067221385<open>)]
[Hibernate] 
    insert 
    into
        discussion_hash_tag
        (discussion_id, hash_tag_id) 
    values
        (?, ?)

실행 계획

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

분석

  • 1번 쿼리(특정 미션 조회) : const(primary) / 기본 키 또는 고유키에 의한 조회
  • 2번 쿼리(특정 멤버 조회) : const(primary) / 기본 키 또는 고유키에 의한 조회
  • 3번 쿼리(해시태그 IN 조회) : range(primary) / 인덱스 특정 범위 행 접근
  • 4번 쿼리(디스커션 삽입) : ALL
  • 5번 쿼리(미션 아이디 기반 미션 해시 태그 조회) : ref(fk_mission_hash_tag_mission) / 고유 키가 아닌 인덱스
  • 6번 쿼리(특정 해시 태그 조회) : const(primry) / 기본 키 또는 고유키에 의한 조회
  • 7번 쿼리(특정 해시 태그 조회) : DicussionWriteService.create N + 1 확인 (save의 반환값으로 응답 만들기 때문)
  • 8번 쿼리(디스커션 해시 태그 삽입) : ALL

솔루션 댓글 목록 조회 ✓

select
        sc1_0.id,
        sc1_0.content,
        sc1_0.created_at,
        sc1_0.deleted_at,
        sc1_0.member_id,
        sc1_0.parent_comment_id,
        sc1_0.solution_id 
    from
        solution_comment sc1_0 
    where
        sc1_0.solution_id=? 
    order by
        sc1_0.created_at
2024-09-25 17:56:56.083 | http-nio-8080-exec-8 | traceId=de8e5400-5b25-44fa-ae5d-1b1a4aa405dc | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
[Hibernate] 
    select
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id 
    from
        member m1_0 
    where
        m1_0.id=?

실행 계획

image.png

image.png

분석

  • 1번 쿼리(특정 solution comment 조회) : ref(fk_solution_comment_solution) / 고유 키가 아닌 인덱스 / filesort → create_at 정렬
  • 2번 쿼리(사용자 조회) : const(primary) / 기본 키 또는 고유키에 의한 조회

솔루션 댓글 추가 ✓

 select
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id 
    from
        member m1_0 
    where
        m1_0.id=?
2024-09-25 17:58:28.722 | http-nio-8080-exec-10 | traceId=3909f511-eca4-4ceb-8362-182b3674087b | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
2024-09-25 17:58:28.724 | http-nio-8080-exec-10 | traceId=3909f511-eca4-4ceb-8362-182b3674087b | DEBUG | o.s.orm.jpa.JpaTransactionManager | Found thread-bound EntityManager [SessionImpl(1904910575<open>)] for JPA transaction
2024-09-25 17:58:28.724 | http-nio-8080-exec-10 | traceId=3909f511-eca4-4ceb-8362-182b3674087b | DEBUG | o.s.orm.jpa.JpaTransactionManager | Participating in existing transaction
[Hibernate] 
    select
        s1_0.id,
        s1_0.created_at,
        s1_0.description,
        s1_0.member_id,
        s1_0.mission_id,
        s1_0.url,
        s1_0.status,
        s1_0.title 
    from
        solution s1_0 
    where
        s1_0.id=?
2024-09-25 17:58:28.725 | http-nio-8080-exec-10 | traceId=3909f511-eca4-4ceb-8362-182b3674087b | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
2024-09-25 17:58:28.727 | http-nio-8080-exec-10 | traceId=3909f511-eca4-4ceb-8362-182b3674087b | DEBUG | o.s.orm.jpa.JpaTransactionManager | Found thread-bound EntityManager [SessionImpl(1904910575<open>)] for JPA transaction
2024-09-25 17:58:28.727 | http-nio-8080-exec-10 | traceId=3909f511-eca4-4ceb-8362-182b3674087b | DEBUG | o.s.orm.jpa.JpaTransactionManager | Participating in existing transaction
[Hibernate] 
    insert 
    into
        solution_comment
        (content, created_at, deleted_at, member_id, parent_comment_id, solution_id, id) 
    values
        (?, ?, ?, ?, ?, ?, default)

실행 계획

image.png

image.png

image.png

분석

  • 1번 쿼리(특정 사용자 조회) : const(primary) / 기본 키 또는 고유키에 의한 조회
  • 2번 쿼리(특정 솔루션 조회) : const(primary) / 기본 키 또는 고유키에 의한 조회
  • 3번 쿼리(특정 댓글 삽입) : ALL / 실행 계획 볼 수 있는 곳인가?

솔루션 댓글 삭제(논리삭제) ✓

select
        sc1_0.id,
        sc1_0.content,
        sc1_0.created_at,
        sc1_0.deleted_at,
        sc1_0.member_id,
        sc1_0.parent_comment_id,
        sc1_0.solution_id 
    from
        solution_comment sc1_0 
    where
        sc1_0.id=?
2024-09-25 17:59:24.460 | http-nio-8080-exec-1 | traceId=e8b3ed3e-7ead-4a74-baa8-2efcd54ee920 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
2024-09-25 17:59:24.462 | http-nio-8080-exec-1 | traceId=e8b3ed3e-7ead-4a74-baa8-2efcd54ee920 | DEBUG | o.s.orm.jpa.JpaTransactionManager | Initiating transaction commit
2024-09-25 17:59:24.462 | http-nio-8080-exec-1 | traceId=e8b3ed3e-7ead-4a74-baa8-2efcd54ee920 | DEBUG | o.s.orm.jpa.JpaTransactionManager | Committing JPA transaction on EntityManager [SessionImpl(505973396<open>)]
[Hibernate] 
    update
        solution_comment 
    set
        content=?,
        deleted_at=?,
        member_id=?,
        parent_comment_id=?,
        solution_id=? 
    where
        id=?

실행 계획

image.png

image.png

분석

  • 1번 쿼리(특정 solution comment 조회) : const(primary) / 기본 키 또는 고유키에 의한 조회
  • 2번 쿼리(특정 댓글 업데이트) : range(primary) / 인덱스 특정 범위 행 접근

사용자가 솔루션에 단 댓글 목록 조회 ✓

 select
        sc1_0.id,
        sc1_0.solution_id,
        sc1_0.content,
        sc1_0.created_at,
        s1_0.title,
        count(sc1_0.id) 
    from
        solution_comment sc1_0 
    join
        solution s1_0 
            on s1_0.id=sc1_0.solution_id 
    where
        sc1_0.member_id=? 
        and sc1_0.deleted_at is null 
    group by
        sc1_0.id

실행 계획

image.png

분석

  • 실행 계획 1 : solution_comment / ref(fk_solution_comment_member) / 고유 키가 아닌 인덱스
  • 실행 계획 2 : solution / eq_ref(primary) / 조인에서 처음 읽은 테이블의 칼럼값을, 다음 읽을 테이블의 PK나 유니크 키 칼럼의 검색 조건에 사용

미션 목록 조회 ✓

select
        distinct m1_0.id,
        ht1_0.mission_id,
        ht1_0.id,
        ht1_0.hash_tag_id,
        ht2_0.id,
        ht2_0.name,
        m1_0.url,
        m1_0.summary,
        m1_0.thumbnail,
        m1_0.title 
    from
        mission m1_0 
    join
        mission_hash_tag ht1_0 
            on m1_0.id=ht1_0.mission_id 
    join
        hash_tag ht2_0 
            on ht2_0.id=ht1_0.hash_tag_id 
    where
        exists(select
            1 
        from
            mission_hash_tag mht1_0 
        join
            hash_tag ht3_0 
                on ht3_0.id=mht1_0.hash_tag_id 
        where
            mht1_0.mission_id=m1_0.id 
            and (lower(?)='all' 
            or ht3_0.name=?)) 
    order by
        ht1_0.id

실행 계획

image.png

분석

-> Sort: ht1_0.id  (actual time=8.057..8.073 rows=20 loops=1)
    -> Table scan on <temporary>  (cost=0.04..3.76 rows=102) (actual time=0.005..0.026 rows=20 loops=1)
        -> Temporary table with deduplication  (cost=168.31..172.03 rows=102) (actual time=7.170..7.197 rows=20 loops=1)
            -> Nested loop inner join  (cost=158.11 rows=102) (actual time=5.318..6.166 rows=20 loops=1)
                -> Nested loop inner join  (cost=122.55 rows=102) (actual time=5.197..5.876 rows=20 loops=1)
                    -> Nested loop inner join  (cost=111.65 rows=34) (actual time=4.770..4.961 rows=6 loops=1)
                        -> Table scan on <subquery2>  (cost=0.09..2.92 rows=34) (actual time=0.005..0.008 rows=6 loops=1)
                            -> Materialize with deduplication  (cost=26.40..29.24 rows=34) (actual time=4.439..4.445 rows=6 loops=1)
                                -> Nested loop inner join  (cost=22.91 rows=34) (actual time=3.719..4.134 rows=6 loops=1)
                                    -> Filter: (ht3_0.`name` = '객체지향')  (cost=11.25 rows=11) (actual time=3.288..3.462 rows=1 loops=1)
                                        **-> Table scan on ht3_0  (cost=11.25 rows=110) (actual time=2.893..3.198 rows=110 loops=1)**
                                    -> Index lookup on mht1_0 using fk_mission_hash_tag_hash_tag (hash_tag_id=ht3_0.id)  (cost=0.78 rows=3) (actual time=0.291..0.419 rows=6 loops=1)
                        -> Single-row index lookup on m1_0 using PRIMARY (id=`<subquery2>`.mission_id)  (cost=79.10 rows=1) (actual time=0.082..0.082 rows=1 loops=6)
                    -> Index lookup on ht1_0 using fk_mission_hash_tag_mission (mission_id=`<subquery2>`.mission_id)  (cost=1.04 rows=3) (actual time=0.143..0.150 rows=3 loops=6)
                -> Single-row index lookup on ht2_0 using PRIMARY (id=ht1_0.hash_tag_id)  (cost=8.55 rows=1) (actual time=0.013..0.013 rows=1 loops=20)
  • 서브 쿼리 내부 hash_tag 테이블 스캔

미션 단건 조회 ✓

select
        distinct m1_0.id,
        ht1_0.mission_id,
        ht1_0.id,
        ht1_0.hash_tag_id,
        ht2_0.id,
        ht2_0.name,
        m1_0.url,
        m1_0.summary,
        m1_0.thumbnail,
        m1_0.title 
    from
        mission m1_0 
    join
        mission_hash_tag ht1_0 
            on m1_0.id=ht1_0.mission_id 
    join
        hash_tag ht2_0 
            on ht2_0.id=ht1_0.hash_tag_id 
    where
        m1_0.id=? 
    order by
        ht1_0.id

실행 계획

image.png

분석

  • 실행 계획 1 : mission / const(primary) / 기본 키 또는 고유키에 의한 조회
  • 실행 계획 2 : mission_hash_tag / ref(fk_mission_hash_tag_mission) / 고유 키가 아닌 인덱스
  • 실행 계획 3 : hash_tag / eq_ref(primary) / 조인에서 처음 읽은 테이블의 칼럼값을, 다음 읽을 테이블의 PK나 유니크 키 칼럼의 검색 조건에 사용

사용자가 시작한 미션 목록 조회 API ✓

select
        s1_0.id,
        s1_0.created_at,
        s1_0.description,
        s1_0.member_id,
        s1_0.mission_id,
        s1_0.url,
        s1_0.status,
        s1_0.title 
    from
        solution s1_0 
    where
        s1_0.member_id=? 
        and s1_0.status=?
2024-09-25 19:59:06.683 | http-nio-8080-exec-2 | traceId=cbea50b2-9e6d-415f-aea3-16124b2c90b2 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
2024-09-25 19:59:06.683 | http-nio-8080-exec-2 | traceId=cbea50b2-9e6d-415f-aea3-16124b2c90b2 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (2:VARCHAR) <- [IN_PROGRESS]
[Hibernate] 
    select
        m1_0.id,
        m1_0.url,
        m1_0.summary,
        m1_0.thumbnail,
        m1_0.title 
    from
        mission m1_0 
    where
        m1_0.id=?
2024-09-25 19:59:06.684 | http-nio-8080-exec-2 | traceId=cbea50b2-9e6d-415f-aea3-16124b2c90b2 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
[Hibernate] 
    select
        ht1_0.mission_id,
        ht1_0.id,
        ht1_0.hash_tag_id 
    from
        mission_hash_tag ht1_0 
    where
        ht1_0.mission_id=? 
    order by
        ht1_0.id
2024-09-25 19:59:06.685 | http-nio-8080-exec-2 | traceId=cbea50b2-9e6d-415f-aea3-16124b2c90b2 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
[Hibernate] 
    select
        ht1_0.id,
        ht1_0.name 
    from
        hash_tag ht1_0 
    where
        ht1_0.id=?
2024-09-25 19:59:06.685 | http-nio-8080-exec-2 | traceId=cbea50b2-9e6d-415f-aea3-16124b2c90b2 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [1]
[Hibernate] 
    select
        ht1_0.id,
        ht1_0.name 
    from
        hash_tag ht1_0 
    where
        ht1_0.id=?
2024-09-25 19:59:06.686 | http-nio-8080-exec-2 | traceId=cbea50b2-9e6d-415f-aea3-16124b2c90b2 | TRACE | org.hibernate.orm.jdbc.bind | binding parameter (1:BIGINT) <- [2]
[Hibernate] 
    select
        ht1_0.id,
        ht1_0.name 
    from
        hash_tag ht1_0 
    where
        ht1_0.id=?

실행 계획

image.png

image.png

image.png

image.png

image.png

image.png

분석

1번 쿼리(특정 사용자의 status 기반 solution 조회)/ ref(fk_solution_member) / 고유 키가 아닌 인덱스

2번 쿼리(사용자 조회) : const(primary) / 기본 키 또는 고유키에 의한 조회

3번 쿼리(특정 미션에 태그된 해시 태그 조회) : ref(fk_mission_hash_tag_mission) / 고유 키가 아닌 인덱스

4번 쿼리(특정 해시 태그 조회) : const(primry) / 기본 키 또는 고유키에 의한 조회

5번 쿼리(특정 해시 태그 조회) : N + 1 (SolutionRepository.findAllByMember_IdAndStatus)

6번 쿼리(특정 해시 태그 조회) : N + 1

내 정보 조회 → const ✓

select
        m1_0.id,
        m1_0.created_at,
        m1_0.email,
        m1_0.image_url,
        m1_0.name,
        m1_0.provider,
        m1_0.social_id 
    from
        member m1_0 
    where
        m1_0.id=?

image.png

해시태그 목록 조회 → rows가 낮아 풀 테이블 스캔 ✓

select
        ht1_0.id,
        ht1_0.name 
    from
        hash_tag ht1_0

image.png

Clone this wiki locally