Skip to content
튜닝 연습소트 튜닝과 페이징
SORT-050

「재주문 간격 분포」 다섯 줄을 내는 데 2초 가까이 걸린다

기초SELECT한빛유통355 / 700

상황 ​

마케팅팀의 「재주문 간격 분포」는 2024년 주문마다 같은 고객의 직전 주문과 며칠 떨어졌는지를 구해 다섯 구간(0일, 1일, 2 ~ 3일, 4 ~ 7일, 8일 이상)으로 센다. 매주 월요일 회의 자료에 붙이고, 담당자가 구간을 바꿔 가며 하루 수십 번 돌린다. 다섯 줄짜리 결과인데 2초 가까이 걸린다. 개발자는 "고객마다 주문에 번호를 매기고, n번 주문과 n+1번 주문을 짝지었다"라고 설명한다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT는 전부 자정 · ORD_SEQ는 유일하다. 2024년 주문은 333,245건이고 고객 2,000명 모두 2024년 주문이 있다(고객당 약 167건).
  • 같은 고객이 같은 날 두 번 이상 주문하는 일이 흔하다. 1년 치(33%)를 읽으므로 인덱스보다 FULL이 싸다.
  • 통계는 최신이다.

원본 SQL ​

sql
SELECT CASE WHEN gap = 0  THEN '0일'
            WHEN gap = 1  THEN '1일'
            WHEN gap <= 3 THEN '2~3일'
            WHEN gap <= 7 THEN '4~7일'
            ELSE '8일 이상' END AS gap_band,
       COUNT(*) AS ord_cnt
  FROM (SELECT b.ORD_DT - a.ORD_DT AS gap
          FROM (SELECT CUS_ID, ORD_DT,
                       ROW_NUMBER() OVER (PARTITION BY CUS_ID ORDER BY ORD_DT) AS rn
                  FROM T_ORD_BIG
                 WHERE ORD_DT >= DATE '2024-01-01'
                   AND ORD_DT <  DATE '2025-01-01') a,
               (SELECT CUS_ID, ORD_DT,
                       ROW_NUMBER() OVER (PARTITION BY CUS_ID ORDER BY ORD_DT) AS rn
                  FROM T_ORD_BIG
                 WHERE ORD_DT >= DATE '2024-01-01'
                   AND ORD_DT <  DATE '2025-01-01') b
         WHERE b.CUS_ID = a.CUS_ID
           AND b.rn     = a.rn + 1)
 GROUP BY CASE WHEN gap = 0  THEN '0일'
               WHEN gap = 1  THEN '1일'
               WHEN gap <= 3 THEN '2~3일'
               WHEN gap <= 7 THEN '4~7일'
               ELSE '8일 이상' END

실행계획 학습용 예시 ​

xplan
---------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |           |      1 |        |      5 |00:00:01.93 |   11652 |          |
|   1 |  HASH GROUP BY         |           |      1 |      5 |      5 |00:00:01.93 |   11652 | 1035K (0)|
|*  2 |   HASH JOIN            |           |      1 |    333K|    331K|00:00:01.81 |   11652 |   12M (0)|
|   3 |    VIEW                |           |      1 |    333K|    333K|00:00:00.52 |    5826 |          |
|   4 |     WINDOW SORT        |           |      1 |    333K|    333K|00:00:00.47 |    5826 |   10M (0)|
|*  5 |      TABLE ACCESS FULL | T_ORD_BIG |      1 |    333K|    333K|00:00:00.09 |    5826 |          |
|   6 |    VIEW                |           |      1 |    333K|    333K|00:00:00.55 |    5826 |          |
|   7 |     WINDOW SORT        |           |      1 |    333K|    333K|00:00:00.49 |    5826 |   10M (0)|
|*  8 |      TABLE ACCESS FULL | T_ORD_BIG |      1 |    333K|    333K|00:00:00.09 |    5826 |          |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("B"."CUS_ID"="A"."CUS_ID" AND "B"."RN"="A"."RN"+1)
   5 - filter(("ORD_DT">=TO_DATE(' 2024-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "ORD_DT"<TO_DATE(' 2025-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))
   8 - filter(("ORD_DT">=TO_DATE(' 2024-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "ORD_DT"<TO_DATE(' 2025-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))

문제 ​

  1. 실행계획에서 같은 일을 두 번 하는 곳을 찾고, 2번 줄 HASH JOIN이 무엇과 무엇을 맞추는지 설명하시오.
  2. 2024년 주문을 한 번만 읽고 한 번만 정렬하도록 SQL을 고치시오. 같은 날 주문이 여럿일 때 순서를 어떻게 정할지도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자