Skip to content
튜닝 연습옵티마이저와 통계
OPT-007

월 상품 실적 MERGE가 매달 1일에만 블록 18만 개를 읽는다

중급MERGE유통597 / 700

상황 ​

본사 레거시 유통의 「월 상품 실적」 배치는 매달 1일 03:00에 돈다. 매장 POS가 보낸 지난달 판매상품 파일을 작업 테이블 W_SLE_MON에 올리고(sqlldr 일반 경로), 매장 · 상품별로 묶어 CF19에 MERGE한 뒤, 저장 공간을 돌려주려고 작업 테이블을 비운다. DBA의 야간 잡 PKG_STATS.NIGHTLY(매일 23:40)는 목록 표 STATS_TARGET의 테이블을 하나씩 GATHER_TABLE_STATS로 모은다. W_SLE_MON은 배치를 만들 때 목록에 넣었다. 선배가 물었다. "그 잡이 이 테이블을 모을 때 테이블에 뭐가 들어 있었을까?"

테이블과 인덱스 ​

  • W_SLE_MON 월 판매상품 작업 테이블(이 문제용): SLE_NO · ITM_NO · SLE_QTY · SLE_UPRC · 채웠을 때 약 45,900행(6월분 45,912행) · 342블록 · 인덱스 없음
  • W_SLE_MON 통계: NUM_ROWS 0 · BLOCKS 0 · 마지막 수집 6월 30일 23:40(야간 잡 · 달마다 채운 지 몇 분 만에 비우므로 야간 잡은 늘 빈 테이블을 본다)
  • CF10 판매: 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · 6월 판매 20,953건 · 통계 최신
  • CF19 상품 월 실적(이 문제용): 매장 6 × 상품 약 109 × 월 · 달마다 약 654행 · PK (SHP_NO, ITM_NO, SLE_YM) · 28블록
  • 야간 잡은 테이블마다 따로 GATHER_TABLE_STATS를 부른다. 달마다 크기는 45,000행 안팎으로 비슷하다.

원본 SQL ​

sql
-- 매달 1일 03:00 · 월 상품 실적
--   ① sqlldr 로 W_SLE_MON 에 지난달 판매상품 파일을 올린다 (일반 경로 · 약 45,900행)
--   ② MERGE
MERGE INTO CF19 t
USING (SELECT a.SHP_NO, w.ITM_NO,
              SUM(w.SLE_QTY) AS qty, SUM(w.SLE_QTY * w.SLE_UPRC) AS amt
         FROM W_SLE_MON w, CF10 a
        WHERE a.SLE_NO = w.SLE_NO
        GROUP BY a.SHP_NO, w.ITM_NO) s
   ON (t.SHP_NO = s.SHP_NO AND t.ITM_NO = s.ITM_NO AND t.SLE_YM = :ym)
 WHEN MATCHED THEN
   UPDATE SET t.SLE_QTY = s.qty, t.SLE_AMT = s.amt
 WHEN NOT MATCHED THEN
   INSERT (SHP_NO, ITM_NO, SLE_YM, SLE_QTY, SLE_AMT)
   VALUES (s.SHP_NO, s.ITM_NO, :ym, s.qty, s.amt);
COMMIT;
--   ③ 저장 공간을 돌려준다
TRUNCATE TABLE W_SLE_MON;

실행계획 학습용 예시 ​

xplan
[7월 1일 03:00 · ② MERGE]
-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads |
-------------------------------------------------------------------------------------------------------------------
|   0 | MERGE STATEMENT                     |           |      1 |        |      0 |00:00:01.86 |    186K |   312 |
|   1 |  MERGE                              | CF19      |      1 |        |      0 |00:00:01.86 |    186K |   312 |
|   2 |   VIEW                              |           |      1 |        |    654 |00:00:01.84 |    184K |   312 |
|*  3 |    HASH JOIN OUTER                  |           |      1 |      1 |    654 |00:00:01.84 |    184K |   312 |
|   4 |     VIEW                            |           |      1 |      1 |    654 |00:00:01.83 |    184K |   312 |
|   5 |      HASH GROUP BY                  |           |      1 |      1 |    654 |00:00:01.83 |    184K |   312 |
|   6 |       NESTED LOOPS                  |           |      1 |      1 |  45912 |00:00:01.79 |    184K |   312 |
|   7 |        NESTED LOOPS                 |           |      1 |      1 |  45912 |00:00:00.93 |    138K |   118 |
|   8 |         TABLE ACCESS FULL           | W_SLE_MON |      1 |      1 |  45912 |00:00:00.01 |     342 |     0 |
|*  9 |         INDEX UNIQUE SCAN           | CF10_PK   |  45912 |      1 |  45912 |00:00:00.88 |    138K |   118 |
|  10 |        TABLE ACCESS BY INDEX ROWID  | CF10      |  45912 |      1 |  45912 |00:00:00.81 |   45912 |   194 |
|  11 |     TABLE ACCESS FULL               | CF19      |      1 |    654 |   3924 |00:00:00.01 |      28 |     0 |
-------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("T"."SLE_YM"=:YM AND "T"."ITM_NO"="S"."ITM_NO" AND "T"."SHP_NO"="S"."SHP_NO")
   9 - access("A"."SLE_NO"="W"."SLE_NO")

[USER_TAB_STATISTICS — W_SLE_MON]
 NUM_ROWS  BLOCKS  LAST_ANALYZED   STALE_STATS   STATTYPE_LOCKED
        0       0  06-30 23:40     NO            -

문제 ​

  1. 8번 줄의 E-Rows 1이 어떻게 만들어졌는지 배치와 야간 잡의 시각으로 설명하고, 그 때문에 9번과 10번 줄에서 일어난 일을 숫자로 쓰시오.
  2. 이 테이블의 통계를 대표 크기로 잠그는 방법을 SQL로 쓰시오. 야간 잡과 부딪치는 곳과 잠금이 맞는 조건도 쓰시오.
  3. 다른 방법(끝에 비우지 않기, 적재 직후 수집, 통계를 지우고 잠가 동적 샘플링)과 견주시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자