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

주문 583건의 '구매 직전에 본 상품'을 찾는 데 조회 로그를 583번 통째로 읽고, 14건은 결과에 없다

중급SELECT이커머스205 / 700

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

상황 ​

마케팅팀의 「구매 직전 조회 상품」 분석은 주문마다 그 고객이 주문하기 전에 마지막으로 본 상품을 붙여, 마지막으로 본 상품을 실제로 산 비율을 낸다. 담당자가 달을 바꿔 가며 돌리고, 한 번에 2.4초가 걸린다. 결과 건수가 그달 주문 수와 다르다는 것도 문제다. 2024년 6월은 주문이 583건인데 결과는 569행이다. 담당자는 "빠진 14건은 어디로 갔는지 모르겠다"라고 한다.

테이블과 인덱스 ​

  • orders: 34,908행 · 193블록 · PK(order_id)만 있음 · order_date DATE(시분초 있음) · 2024년 6월 583건(고객 561명)
  • product_views: 100,000행 · 367블록 · PK(view_id)만 있음 · customer_id는 비회원 조회면 NULL(약 20%) · view_date DATE(시분초 있음) · 회원 조회는 고객 한 명에 평균 15건
  • 두 테이블 모두 customer_id에 인덱스가 없다 · 인덱스는 승인 절차를 거쳐야 만들 수 있다 · 통계 최신
  • 6월 주문 가운데 14건은 주문 전에 남은 조회 기록이 없다(앱 첫 화면에서 바로 재구매한 고객)

원본 SQL ​

sql
SELECT o.order_id, o.customer_id, o.order_date,
       v.product_id AS last_view_pid, v.view_date AS last_view_date
  FROM orders o, product_views v
 WHERE o.order_date >= TO_DATE(:v_from, 'YYYYMMDD')
   AND o.order_date <  TO_DATE(:v_to, 'YYYYMMDD')
   AND v.customer_id = o.customer_id
   AND v.view_date = (SELECT MAX(x.view_date)
                        FROM product_views x
                       WHERE x.customer_id = o.customer_id
                         AND x.view_date  <= o.order_date)

실행계획 학습용 예시 ​

xplan
---------------------------------------------------------------------------------------------------
| Id  | Operation           | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |               |      1 |        |    569 |00:00:02.41 |     214K|
|*  1 |  FILTER             |               |      1 |        |    569 |00:00:02.41 |     214K|
|*  2 |   HASH JOIN         |               |      1 |   2230 |   8812 |00:00:00.04 |     560 |
|*  3 |    TABLE ACCESS FULL| ORDERS        |      1 |    583 |    583 |00:00:00.01 |     193 |
|*  4 |    TABLE ACCESS FULL| PRODUCT_VIEWS |      1 |  80000 |  80000 |00:00:00.02 |     367 |
|   5 |   SORT AGGREGATE    |               |    583 |      1 |    583 |00:00:02.36 |     213K|
|*  6 |    TABLE ACCESS FULL| PRODUCT_VIEWS |    583 |      8 |   4664 |00:00:02.35 |     213K|
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("V"."VIEW_DATE"=)
   2 - access("V"."CUSTOMER_ID"="O"."CUSTOMER_ID")
   3 - filter(("O"."ORDER_DATE">=TO_DATE(:V_FROM,'YYYYMMDD') AND "O"."ORDER_DATE"<TO_DATE(:V_TO,'YYYYMMDD')))
   4 - filter("V"."CUSTOMER_ID" IS NOT NULL)
   6 - filter(("X"."CUSTOMER_ID"=:B1 AND "X"."VIEW_DATE"<=:B2))

문제 ​

  1. 213K 블록이 어디서 나오는지 5번과 6번 줄의 Starts로 설명하고, 결과에서 14건이 빠진 까닭도 쓰시오.
  2. 인덱스를 만들지 않고 두 테이블을 한 번씩만 읽으면서, 주문마다 직전 조회 한 건을 붙이고 583건을 모두 내도록 SQL을 고치시오.
  3. product_views (customer_id, view_date) 인덱스를 만들고 주문마다 한 건에서 멈추는 안과 견주어, 이 분석에는 어느 쪽이 맞는지 숫자로 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자