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

연말 매출 요약 배치를 두고 개발자는 해시 조인이, DBA는 NL 조인이 빠르다고 한다

실전INSERT한빛유통222 / 700

상황 ​

정보기획팀의 연말 배치는 한 해 동안 A, B 등급 고객이 산 신선과 냉장 상품의 매출을 등급, 지역, 상품 유형별로 묶어 요약 테이블에 넣는다. 개발자는 FROM 절 순서대로 LEADING을 적고 다섯 테이블을 모두 해시로 붙였다(A안). DBA는 "등급 조건이 10%만 남기니 고객부터 NL로 가야 한다"며 시험 DB에서 B안을 돌려 보았다. 박팀장은 두 안과, 다른 안이 있다면 그것까지 숫자로 비교해 하나를 고르고 그 결정이 언제 바뀌어야 하는지도 적어 달라고 했다. 아래는 A안으로 2024년을 돌린 실행계획이다.

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · IX_T_ORD_CUS_DT (CUS_ID, ORD_DT) · 2024년 주문 50,000건(테이블 전체에 흩어져 있다), 그중 A, B 등급 고객의 주문 5,003건
  • T_ORD_DET: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · 2024년 주문의 상세 99,934줄, A, B 등급 몫 9,968줄
  • M_ITM: 100행 · 5블록 · ITM_TP 4종(신선 'FRESH'과 냉장 'CHILL'이 합쳐 50개) / M_CUS: 2,000행 · 30블록 · A, B 등급 200명 / M_RGN: 8행 · 4블록
  • B안(DBA, 시험 DB에서 블록이 모두 캐시에 올라온 상태로 잼): LEADING(c o d i r) USE_NL(o d) · Buffers 20,268 · 0.11초 · T_ORD를 IX_T_ORD_CUS_DT로, 주문상세를 PK로 각각 5,003번 찾는다
  • 운영의 연말 배치 시간대에는 T_ORD와 T_ORD_DET 블록 대부분이 버퍼 캐시에 없다 · 통계 최신

원본 SQL ​

sql
INSERT INTO T_SLS_GD_RGN_TP (BAS_YY, CUS_GD, RGN_NM, ITM_TP, DET_CNT, SLE_AMT)
SELECT /*+ LEADING(o d i c r) USE_HASH(d i c r) */
       :v_yy, c.CUS_GD, r.RGN_NM, i.ITM_TP, COUNT(*), SUM(d.ORD_QTY * d.UNT_PRC)
  FROM T_ORD o, T_ORD_DET d, M_ITM i, M_CUS c, M_RGN r
 WHERE o.ORD_DT >= TO_DATE(:v_yy || '0101', 'YYYYMMDD')
   AND o.ORD_DT <  ADD_MONTHS(TO_DATE(:v_yy || '0101', 'YYYYMMDD'), 12)
   AND d.ORD_SEQ = o.ORD_SEQ
   AND i.ITM_ID  = d.ITM_ID
   AND i.ITM_TP IN ('FRESH', 'CHILL')
   AND c.CUS_ID  = o.CUS_ID
   AND c.CUS_GD IN ('A', 'B')
   AND r.RGN_ID  = c.RGN_ID
 GROUP BY c.CUS_GD, r.RGN_NM, i.ITM_TP

실행계획 학습용 예시 ​

xplan
-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name            | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  Used-Mem |
-------------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT          |                 |      1 |        |      0 |00:00:00.27 |    1856 |           |
|   1 |  LOAD TABLE CONVENTIONAL  | T_SLS_GD_RGN_TP |      1 |        |      0 |00:00:00.27 |    1856 |           |
|   2 |   HASH GROUP BY           |                 |      1 |     32 |     32 |00:00:00.26 |    1831 | 1156K (0) |
|*  3 |    HASH JOIN              |                 |      1 |   5000 |   4987 |00:00:00.26 |    1831 | 1290K (0) |
|   4 |     TABLE ACCESS FULL     | M_RGN           |      1 |      8 |      8 |00:00:00.01 |       4 |           |
|*  5 |     HASH JOIN             |                 |      1 |   5000 |   4987 |00:00:00.26 |    1827 | 1347K (0) |
|*  6 |      TABLE ACCESS FULL    | M_CUS           |      1 |    200 |    200 |00:00:00.01 |      30 |           |
|*  7 |      HASH JOIN            |                 |      1 |  50000 |  49871 |00:00:00.24 |    1797 | 1236K (0) |
|*  8 |       TABLE ACCESS FULL   | M_ITM           |      1 |     50 |     50 |00:00:00.01 |       5 |           |
|*  9 |       HASH JOIN           |                 |      1 |   100K |  99934 |00:00:00.19 |    1792 | 4417K (0) |
|* 10 |        TABLE ACCESS FULL  | T_ORD           |      1 |  50000 |  50000 |00:00:00.01 |     896 |           |
|  11 |        TABLE ACCESS FULL  | T_ORD_DET       |      1 |   200K |   200K |00:00:00.04 |     896 |           |
-------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("R"."RGN_ID"="C"."RGN_ID")
   5 - access("C"."CUS_ID"="O"."CUS_ID")
   6 - filter(("C"."CUS_GD"='A' OR "C"."CUS_GD"='B'))
   7 - access("I"."ITM_ID"="D"."ITM_ID")
   8 - filter(("I"."ITM_TP"='CHILL' OR "I"."ITM_TP"='FRESH'))
   9 - access("D"."ORD_SEQ"="O"."ORD_SEQ")
  10 - filter(("O"."ORD_DT">=TO_DATE(:V_YY||'0101','YYYYMMDD') AND "O"."ORD_DT"<ADD_MONTHS(TO_DATE(:V_YY||'0101','YYYYMMDD'),12)))

문제 ​

  1. A안의 실행계획에서 조인마다 흐르는 행 수를 줄 번호로 짚고, 일량이 어디서 나오는지 Buffers와 A-Rows로 설명하시오.
  2. A안, B안, 그리고 고객 조건을 먼저 적용하는 해시 조인 안(C안)을 Buffers, 조인에 흐르는 행 수, 물리 I/O 위험으로 비교하고, 하나를 골라 SQL을 작성하시오.
  3. 고른 안이 틀린 답이 되는 조건(등급 조건이나 기간이 바뀔 때)을 쓰고, 이 배치에 힌트를 남길지 판단하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자