테마
정보기획팀의 연말 배치는 한 해 동안 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블록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 블록 대부분이 버퍼 캐시에 없다 · 통계 최신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
-------------------------------------------------------------------------------------------------------------------
| 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)))