Skip to content
튜닝 연습조인 튜닝
JOIN-061

카테고리 페이지의 「이 분류 인기 상품」이 491블록만 읽는데 한 번에 0.1초 가까이 걸린다

기초SELECT이커머스211 / 700

이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.

상황 ​

상품 목록의 카테고리 페이지에는 그 분류의 상품을 배송 완료 기준 판매량 순으로 보여 주는 「이 분류 인기 상품」 칸이 있다. 카테고리를 열 때마다 돌아서 하루 2만 번 호출된다. 개발자는 주문 → 주문상품 → 상품 순서로 해시 조인을 고정해 두었다. 읽는 블록이 491개뿐이라 아무도 의심하지 않았는데, AWR을 보니 이 SQL 하나가 DB CPU의 6%를 쓴다. 아래는 상품 6개가 있는 소분류(category_id = 23)를 연 실행계획이다.

테이블과 인덱스 ​

  • orders: 34,908행 · 193블록 · PK만 있음 · status 6종, 배송 완료('DELIVERED') 24,436건(70%)
  • order_items: 73,498행 · 290블록 · PK만 있음(order_id, product_id에 인덱스 없음) · 소분류 23의 상품 6개가 들어간 줄 1,577줄, 그중 배송 완료 주문의 줄 1,104줄
  • products: 280행 · 8블록 · PK만 있음 · 소분류 하나에 상품 5 ~ 7개
  • 인덱스를 더하려면 승인 절차를 거쳐야 해서 이번에는 SQL만 고친다 · 통계 최신

원본 SQL ​

sql
SELECT /*+ LEADING(o i p) USE_HASH(i p) */
       p.product_id, p.name,
       COUNT(DISTINCT o.order_id) AS ord_cnt, SUM(i.quantity) AS qty
  FROM orders o, order_items i, products p
 WHERE o.status      = 'DELIVERED'
   AND i.order_id    = o.order_id
   AND p.product_id  = i.product_id
   AND p.category_id = :cat
 GROUP BY p.product_id, p.name
 ORDER BY qty DESC

실행계획 학습용 예시 ​

xplan
-------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  Used-Mem |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |             |      1 |        |      6 |00:00:00.09 |     491 |           |
|   1 |  SORT ORDER BY          |             |      1 |      6 |      6 |00:00:00.09 |     491 |  2048 (0) |
|   2 |   HASH GROUP BY         |             |      1 |      6 |      6 |00:00:00.09 |     491 | 1260K (0) |
|*  3 |    HASH JOIN            |             |      1 |   1103 |   1104 |00:00:00.09 |     491 | 1452K (0) |
|*  4 |     TABLE ACCESS FULL   | PRODUCTS    |      1 |      6 |      6 |00:00:00.01 |       8 |           |
|*  5 |     HASH JOIN           |             |      1 |  51449 |  51463 |00:00:00.07 |     483 | 2734K (0) |
|*  6 |      TABLE ACCESS FULL  | ORDERS      |      1 |  24436 |  24436 |00:00:00.01 |     193 |           |
|   7 |      TABLE ACCESS FULL  | ORDER_ITEMS |      1 |  73498 |  73498 |00:00:00.02 |     290 |           |
-------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("P"."PRODUCT_ID"="I"."PRODUCT_ID")
   4 - filter("P"."CATEGORY_ID"=:CAT)
   5 - access("I"."ORDER_ID"="O"."ORDER_ID")
   6 - filter("O"."STATUS"='DELIVERED')

문제 ​

  1. Buffers가 491로 작은데도 시간이 걸리는 이유를 5번 줄과 3번 줄의 A-Rows와 Used-Mem으로 설명하시오.
  2. 조인 순서를 고쳐 SQL을 작성하고, 고친 뒤에 Buffers와 중간 결과가 각각 어떻게 바뀌는지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자