본문 바로가기
💻 개발/주문 플랫폼

11/13 - TIL - Sub Query와 Join

by 컴쏘 2024. 11. 13.

 

Query DSL을 사용해 Cursor 기반으로 가게 메뉴 목록 조회 기능을 구현하던 중, Sub Query를 사용한 방법Join을 사용한 방법의 차이가 궁금해 2가지 방법을 모두 테스트 해보았다.

  1. Shop의 UUID를 통해 Shop의 ID를 조회하고 조회한 ID 값과 일치하는 Product 목록을 조회하는 방법
  2. Join을 사용해 Shop과 Product를 연결하여, Shop의 UUID에 해당하는 shop_id를 가진 Product 목록을 조회하는 방법 

 

2개의 쿼리의 차이를 비교하기 위해 실행 계획으로 살펴보았다. 

 

방법 1. Sub Query 

EXPLAIN ANALYZE
SELECT *
FROM p_product AS p
    where p.shop_entity_shop_id = (
        select s.shop_id
        FROM p_shop s
        where s.uuid = '8c4cc4e0-24b8-480f-99a5-d77337435ad7'
    )
      AND p.product_id > 10
ORDER BY p.product_id ASC, p.created_at ASC
LIMIT 10;

 

결과는 다음과 같다. 

Limit  (cost=18.31..18.32 rows=1 width=4169) (actual time=0.109..0.112 rows=10 loops=1)
  InitPlan 1 (returns $0)
    ->  Index Scan using uk7dljnp9cmxbuvgkkpr5b6i3f1 on p_shop s  (cost=0.14..8.15 rows=1 width=8) (actual time=0.012..0.013 rows=1 loops=1)
          Index Cond: ((uuid)::text = '8c4cc4e0-24b8-480f-99a5-d77337435ad7'::text)
  ->  Sort  (cost=10.16..10.17 rows=1 width=4169) (actual time=0.107..0.109 rows=10 loops=1)
"        Sort Key: p.product_id, p.created_at"
        Sort Method: quicksort  Memory: 30kB
        ->  Seq Scan on p_product p  (cost=0.00..10.15 rows=1 width=4169) (actual time=0.046..0.057 rows=20 loops=1)
              Filter: ((product_id > 10) AND (shop_entity_shop_id = $0))
              Rows Removed by Filter: 10
Planning Time: 1.017 ms
Execution Time: 0.164 ms

 

 

방법 2. Join 

EXPLAIN ANALYZE
SELECT *
FROM p_product AS p
JOIN p_shop AS s ON p.shop_entity_shop_id = s.shop_id
WHERE s.uuid = '8c4cc4e0-24b8-480f-99a5-d77337435ad7'
  AND p.product_id > 10
ORDER BY p.product_id ASC, p.created_at ASC
LIMIT 10;

 

결과는 다음과 같다. 

Limit  (cost=18.33..18.33 rows=1 width=7822) (actual time=0.080..0.081 rows=10 loops=1)
  ->  Sort  (cost=18.33..18.33 rows=1 width=7822) (actual time=0.080..0.080 rows=10 loops=1)
"        Sort Key: p.product_id, p.created_at"
        Sort Method: quicksort  Memory: 35kB
        ->  Nested Loop  (cost=0.14..18.32 rows=1 width=7822) (actual time=0.043..0.055 rows=20 loops=1)
              Join Filter: (p.shop_entity_shop_id = s.shop_id)
              ->  Index Scan using uk7dljnp9cmxbuvgkkpr5b6i3f1 on p_shop s  (cost=0.14..8.15 rows=1 width=3653) (actual time=0.033..0.033 rows=1 loops=1)
                    Index Cond: ((uuid)::text = '8c4cc4e0-24b8-480f-99a5-d77337435ad7'::text)
              ->  Seq Scan on p_product p  (cost=0.00..10.12 rows=3 width=4169) (actual time=0.009..0.016 rows=20 loops=1)
                    Filter: (product_id > 10)
                    Rows Removed by Filter: 10
Planning Time: 0.183 ms
Execution Time: 0.116 ms

 

 

2개의 결과를 비교해보면, 메모리 사용량은 첫 번째 쿼리가 작고, 실행 시간은 두 번째 쿼리가 빠르다. 

  • 지금은 DB에 data가 30개 정도 밖에 없어서 차이가 크지 않은 것 같다. 

 

대규모 데이터셋에서는 Sub Query 방식이 성능 저하를 일으켜 Join 방식을 사용한다고 한다. 

  • Join 방식으로 대체할 수 없는 경우는 Sub Query를 사용해야겠지만, Join으로 대체할 수 있는 경우는 Join을 사용하는 것이 좋다고 한다. (꼭 필요한 경우가 아니라면 Sub Query를 남용하지 않는 것을 권장한다고 한다.) 

 

나중에 대규모 데이터셋을 다루게 될 때 그때 실행 계획으로 다시 살펴봐야겠다.