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

상품을 build로 쓰려고 LEADING 맨 앞에 두었더니 상품 유형별 연 매출이 10초가 됐다

중급SELECT한빛유통175 / 700

상황 ​

「상품 유형별 연 매출」은 한 해 주문의 주문상세를 상품 유형(4종)으로 묶는다. 원래 힌트는 LEADING(o d m) USE_HASH(d m)이었다. 조민우가 06화의 "작은 쪽을 build로"를 읽고, 상품 100행이 build가 되도록 M_ITM을 LEADING 맨 앞으로 옮겼다. 그 뒤로 0.1초이던 리포트가 10초 가까이 걸리고 TEMP 경보가 울린다. Buffers는 거의 그대로라서 조민우는 원인을 못 찾고 있다. 아래는 2024년으로 조회한 실행계획이다.

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · 2024년 주문 50,000건
  • T_ORD_DET: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · 2024년 주문의 상세 100,000줄
  • M_ITM: 100행 · 5블록 · PK_M_ITM (ITM_ID) · ITM_TP 4종 (TP_A ~ TP_D, 25개씩)
  • 통계 최신 · pga_aggregate_target 512M · AUTO

원본 SQL ​

sql
SELECT /*+ LEADING(m o d) USE_HASH(o d) */
       m.ITM_TP, COUNT(*) AS det_cnt, SUM(d.ORD_QTY * d.UNT_PRC) AS sale_amt
  FROM T_ORD o, T_ORD_DET d, M_ITM m
 WHERE o.ORD_DT >= TO_DATE(:v_yyyy || '0101', 'YYYYMMDD')
   AND o.ORD_DT <  ADD_MONTHS(TO_DATE(:v_yyyy || '0101', 'YYYYMMDD'), 12)
   AND d.ORD_SEQ = o.ORD_SEQ
   AND m.ITM_ID  = d.ITM_ID
 GROUP BY m.ITM_TP

실행계획 학습용 예시 · 행 수는 실데이터 ​

xplan
---------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem | Used-Tmp|
---------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |           |      1 |        |      4 |00:00:09.71 |    1797 |       |       |          |         |
|   1 |  HASH GROUP BY         |           |      1 |      4 |      4 |00:00:09.71 |    1797 |  1116K|  1116K|  655K (0)|         |
|*  2 |   HASH JOIN            |           |      1 |    100K|    100K|00:00:09.68 |    1797 |   236M|    14M|   98M (1)|     187M|
|   3 |    MERGE JOIN CARTESIAN|           |      1 |   5000K|   5000K|00:00:01.32 |     901 |       |       |          |         |
|   4 |     TABLE ACCESS FULL  | M_ITM     |      1 |    100 |    100 |00:00:00.01 |       5 |       |       |          |         |
|   5 |     BUFFER SORT        |           |    100 |  50000 |   5000K|00:00:00.71 |     896 |  2316K|   743K| 2058K (0)|         |
|*  6 |      TABLE ACCESS FULL | T_ORD     |      1 |  50000 |  50000 |00:00:00.02 |     896 |       |       |          |         |
|   7 |    TABLE ACCESS FULL   | T_ORD_DET |      1 |    200K|    200K|00:00:00.03 |     896 |       |       |          |         |
---------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("D"."ORD_SEQ"="O"."ORD_SEQ" AND "M"."ITM_ID"="D"."ITM_ID")
   6 - filter(("O"."ORD_DT">=TO_DATE(:V_YYYY||'0101','YYYYMMDD') AND
              "O"."ORD_DT"<ADD_MONTHS(TO_DATE(:V_YYYY||'0101','YYYYMMDD'),12)))

문제 ​

  1. 3번 줄 MERGE JOIN CARTESIAN이 왜 생겼는지, 그 5,000K 행이 어디서 나왔는지 설명하시오. Buffers는 거의 같은데 9.7초가 걸리는 이유를 메모리 열로 설명하시오.
  2. 조인 순서는 주문 → 주문상세로 두고 M_ITM을 build로 쓰도록 힌트를 고치시오. LEADING과 SWAP_JOIN_INPUTS가 각각 무엇을 정하는지 서술하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자