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

같은 템플릿으로 만든 등급 이력 조회 두 개 가운데 VIP 기간 실적만 5초 가까이 걸린다

중급SELECT한빛유통201 / 700

상황 ​

CRM팀의 「VIP 기간 실적」은 2024년에 A 등급이던 동안의 주문만 골라 고객별 건수와 금액을 낸다. 연말 감사 선물 대상을 확정하는 기간이라 하루 수십 번 돌리는데, 한 번에 4.6초가 걸린다. SQL은 사내 템플릿대로 "주문을 먼저 읽고, 등급 이력은 PK로 붙인다"(LEADING(o h) USE_NL(h))는 힌트를 달았다. 같은 템플릿으로 만든 「일자별 주문 목록」(날짜 하나를 고르면 그날 주문 913건에 주문 당시 등급을 붙인다)은 0.01초라서, 개발자는 템플릿 탓이 아니라고 한다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT는 전부 자정 · 2024년 주문 333,245건 · 고객마다 하루 평균 0.46건
  • 인덱스 IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT, ORD_AMT): 작년에 「지역별 고객 월 매출」 화면 때문에 만들었다 · 높이 3 · 리프 약 3,400블록
  • M_CUS_GD_HIS 고객 등급 이력 (이 문제에서 쓰는 파생 테이블, 선분이력): CUS_ID · ST_DT · ED_DT (그날까지 포함, 지금 구간은 DATE '9999-12-31') · CUS_GD · 2022년부터 분기마다 재산정하고 등급이 바뀐 고객만 새 구간을 연다 · 6,140행 · 40블록 · PK_M_CUS_GD_HIS (CUS_ID, ST_DT) (BLEVEL 1)
  • A 등급 구간은 61개이고, 그중 2024년에 걸친 구간은 44개(고객 31명)다 · 통계 최신

원본 SQL ​

sql
SELECT /*+ LEADING(o h) USE_NL(h) */
       o.CUS_ID, COUNT(*) AS ord_cnt, SUM(o.ORD_AMT) AS ord_amt
  FROM T_ORD_BIG o, M_CUS_GD_HIS h
 WHERE o.ORD_DT >= DATE '2024-01-01'
   AND o.ORD_DT <  DATE '2025-01-01'
   AND h.CUS_ID = o.CUS_ID
   AND o.ORD_DT BETWEEN h.ST_DT AND h.ED_DT
   AND h.CUS_GD = 'A'
 GROUP BY o.CUS_ID

실행계획 학습용 예시 ​

xplan
------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name            | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                 |      1 |        |     31 |00:00:04.62 |    1339K|
|   1 |  HASH GROUP BY                |                 |      1 |     31 |     31 |00:00:04.62 |    1339K|
|   2 |   NESTED LOOPS                |                 |      1 |   1667 |   2006 |00:00:04.59 |    1339K|
|   3 |    NESTED LOOPS               |                 |      1 |    833K|    832K|00:00:01.71 |     672K|
|*  4 |     TABLE ACCESS FULL         | T_ORD_BIG       |      1 |    333K|    333K|00:00:00.06 |    5826 |
|*  5 |     INDEX RANGE SCAN          | PK_M_CUS_GD_HIS |    333K|      3 |    832K|00:00:01.32 |     666K|
|*  6 |    TABLE ACCESS BY INDEX ROWID| M_CUS_GD_HIS    |    832K|      1 |   2006 |00:00:02.61 |     667K|
------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - filter(("O"."ORD_DT">=TO_DATE(' 2024-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "O"."ORD_DT"<TO_DATE(' 2025-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))
   5 - access("H"."CUS_ID"="O"."CUS_ID" AND "H"."ST_DT"<="O"."ORD_DT")
   6 - filter(("H"."CUS_GD"='A' AND "H"."ED_DT">="O"."ORD_DT"))

문제 ​

  1. 같은 템플릿인데 「일자별 주문 목록」은 빠르고 이 SQL은 느린 까닭을 Starts와 5번, 6번 줄의 Predicate Information으로 설명하시오.
  2. 어느 테이블에서 출발해야 하는지 정하고 SQL을 고치시오. 출발점을 바꾸면 BETWEEN 조건이 어느 인덱스의 access 조건이 되는지도 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자