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

하루 4만 번 누르는 '최근 구매 상품' 칸이 한 번에 500블록을 읽는다

실전SELECT이커머스156 / 700

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

상황 ​

고객센터 상담 화면의 「최근 구매 상품」 칸은 고객 번호를 넣으면 고객명과 등급, 최근 구매 상품 20줄(주문일 · 상품명 · 카테고리 · 수량 · 금액)을 보여 준다. 상담원 40명이 하루 4만 번 누른다. 18화 졸업 과제의 「주문 내역」(E1) 바로 옆 칸이다. 고객사는 "이 칸 하나를 처음부터 다시 봐 달라. 인덱스는 승인 절차(T2)를 거치면 만들 수 있다"라고 요청했다.

테이블과 인덱스 ​

  • customers: 5,230행 · 51블록 · PK_CUSTOMERS (customer_id) · UQ(email)
  • orders: 34,908행 · 193블록 · PK만 있음 · 고객당 평균 6.7건 · 고객 390은 16건 · order_date 시분초 있음
  • order_items: 73,498행 · 290블록 · PK만 있음 · 주문당 평균 2.1줄 · 고객 390의 구매 줄 35개
  • products 280행 · 8블록, categories 53행 · 4블록 (parent_id 계층) · 둘 다 PK만 있음
  • FK 39개 전부 DISABLED NOT VALIDATED · 조인 키에 인덱스 0개 · 18화에서 shipping.address_id 고아 2,719행이 나왔다 · 통계 최신

원본 SQL ​

sql
SELECT c.name AS cust_name, c.grade, o.order_id, o.order_date,
       p.name AS product_name, g.name AS category_name,
       i.quantity, i.quantity * i.unit_price AS amount
  FROM customers c, orders o, order_items i, products p, categories g
 WHERE c.customer_id = :cust
   AND o.customer_id = c.customer_id
   AND i.order_id    = o.order_id
   AND p.product_id  = i.product_id
   AND g.category_id = p.category_id
 ORDER BY o.order_date DESC
 FETCH FIRST 20 ROWS ONLY

실행계획 학습용 예시 ​

xplan
----------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |              |      1 |        |     20 |00:00:00.02 |     498 |
|*  1 |  VIEW                             |              |      1 |     14 |     20 |00:00:00.02 |     498 |
|*  2 |   WINDOW SORT PUSHED RANK         |              |      1 |     14 |     20 |00:00:00.02 |     498 |
|*  3 |    HASH JOIN                      |              |      1 |     14 |     35 |00:00:00.02 |     498 |
|*  4 |     HASH JOIN                     |              |      1 |     14 |     35 |00:00:00.02 |     494 |
|*  5 |      HASH JOIN                    |              |      1 |     14 |     35 |00:00:00.02 |     486 |
|   6 |       NESTED LOOPS                |              |      1 |      7 |     16 |00:00:00.01 |     196 |
|   7 |        TABLE ACCESS BY INDEX ROWID| CUSTOMERS    |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  8 |         INDEX UNIQUE SCAN         | PK_CUSTOMERS |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  9 |        TABLE ACCESS FULL          | ORDERS       |      1 |      7 |     16 |00:00:00.01 |     193 |
|  10 |       TABLE ACCESS FULL           | ORDER_ITEMS  |      1 |  73498 |  73498 |00:00:00.01 |     290 |
|  11 |      TABLE ACCESS FULL            | PRODUCTS     |      1 |    280 |    280 |00:00:00.01 |       8 |
|  12 |     TABLE ACCESS FULL             | CATEGORIES   |      1 |     53 |     53 |00:00:00.01 |       4 |
----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("from$_subquery$_006"."rowlimit_$$_rownumber"<=20)
   2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("O"."ORDER_DATE") DESC )<=20)
   3 - access("G"."CATEGORY_ID"="P"."CATEGORY_ID")
   4 - access("P"."PRODUCT_ID"="I"."PRODUCT_ID")
   5 - access("I"."ORDER_ID"="O"."ORDER_ID")
   8 - access("C"."CUSTOMER_ID"=:CUST)
   9 - filter("O"."CUSTOMER_ID"=:CUST)

문제 ​

  1. 이 칸을 한 번 조회할 때의 일량이 어디서 나오는지 줄 번호와 Buffers로 설명하고, 하루 4만 번이면 얼마가 되는지 계산하시오.
  2. 조인 순서와 조인 방식, 필요한 인덱스를 정해 개선 SQL과 DDL을 작성하시오. 각 결정의 근거는 행 수로 제시하시오.
  3. 다섯 테이블의 조인마다 '1 쪽이 정말 하나인가'(유일성과 고아)를 점검하시오. FK가 꺼진 이 DB에서 내부 조인을 그대로 둬도 되는지 판단하고, 정렬 키와 인덱스의 대가까지 포함해 서술하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자