Skip to content
튜닝 연습DML과 배치 튜닝
DML-058

작업 테이블을 임시 테이블로 바꾼 첫 달, 월 판매 요약이 에러 없이 0건으로 끝났다

기초PLSQL유통483 / 700

상황 ​

본사 레거시 유통의 월 마감에는 「매장 상품 월 판매 요약」 단계가 있다. 지난달까지는 영구 작업 테이블 CF11_WRK_MON을 DELETE로 비우고 다시 채웠는데, 배치 담당자가 "작업 테이블은 임시 테이블로 바꾸면 redo도 줄고 비울 일도 없다"는 말을 듣고 전역 임시 테이블 GT_SLE_MON으로 바꿨다. 사내 배치 표준의 "단계가 끝날 때마다 커밋한다"는 그대로 지켰다. 12월 1일 02:10에 돈 11월분은 0.4초 만에 SUCCEEDED로 끝났다. 그런데 MD팀 화면의 11월 매장 상품 판매가 한 줄도 나오지 않는다. 에러 로그는 비어 있다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · 2022-01-01 ~ 2023-12-31 · 판매 날짜순 적재 · 2023년 11월 20,712건 · CF10_PK (SLE_NO) · IX_CF10_SLE_DT (SLE_DT)
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · 11월분 45,359행
  • CF01 상품: 109행 · 5블록 · PK ITM_NO · CTG_NO
  • GT_SLE_MON(이 문제용 전역 임시 테이블): 원본 SQL 맨 위의 DDL로 만들었다. 11월에 팔린 매장 × 상품 조합은 531개다(매장 5곳).
  • CF11_SHP_MON_SUM(요약, 이 문제용): PK (SLE_YM, SHP_NO, ITM_NO) · MD팀 화면이 아침에 읽는다.
  • T_BAT_LOG(배치 기록): JOB_NM · STEP · ROWS_DONE · DONE_AT

원본 SQL ​

sql
-- 지난달 바꾼 것: 영구 작업 테이블 CF11_WRK_MON 대신 임시 테이블
CREATE GLOBAL TEMPORARY TABLE GT_SLE_MON (
  SHP_NO  NUMBER(5),
  ITM_NO  VARCHAR2(10),
  SLE_QTY NUMBER,
  SLE_AMT NUMBER
);

PROCEDURE P_SHP_ITM_MON(p_ym VARCHAR2) IS            -- p_ym = '202311'
  v_rows NUMBER;
BEGIN
  -- ⓪ 그 달 요약을 비운다
  DELETE FROM CF11_SHP_MON_SUM WHERE SLE_YM = p_ym;
  COMMIT;                                            -- 사내 배치 표준: 단계가 끝날 때마다 커밋

  -- ① 매장 × 상품 합계를 작업 테이블에
  INSERT INTO GT_SLE_MON (SHP_NO, ITM_NO, SLE_QTY, SLE_AMT)
  SELECT h.SHP_NO, d.ITM_NO, SUM(d.SLE_QTY), SUM(d.SLE_QTY * d.SLE_UPRC)
    FROM CF10 h, CF11 d
   WHERE d.SLE_NO = h.SLE_NO
     AND h.SLE_DT BETWEEN p_ym || '01' AND p_ym || '31'
   GROUP BY h.SHP_NO, d.ITM_NO;
  v_rows := SQL%ROWCOUNT;
  INSERT INTO T_BAT_LOG (JOB_NM, STEP, ROWS_DONE, DONE_AT) VALUES ('SHP_ITM_MON', '1', v_rows, SYSDATE);
  COMMIT;

  -- ② 상품 분류를 붙여 요약 테이블에
  INSERT INTO CF11_SHP_MON_SUM (SLE_YM, SHP_NO, ITM_NO, CTG_NO, SLE_QTY, SLE_AMT)
  SELECT p_ym, g.SHP_NO, g.ITM_NO, i.CTG_NO, g.SLE_QTY, g.SLE_AMT
    FROM GT_SLE_MON g, CF01 i
   WHERE i.ITM_NO = g.ITM_NO;
  v_rows := SQL%ROWCOUNT;
  INSERT INTO T_BAT_LOG (JOB_NM, STEP, ROWS_DONE, DONE_AT) VALUES ('SHP_ITM_MON', '2', v_rows, SYSDATE);
  COMMIT;
END;

실행계획 학습용 예시 ​

xplan
[배치 기록 T_BAT_LOG — 12월 1일 · 11월분]
  STEP   ROWS_DONE   DONE_AT
  1            531   02:10:03
  2              0   02:10:03
  스케줄러 상태: SUCCEEDED

[② 문장 — DISPLAY_CURSOR(FORMAT => 'ALLSTATS LAST')]
------------------------------------------------------------------------------------------------
| Id  | Operation                | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |                  |      1 |        |      0 |00:00:00.01 |       2 |
|   1 |  LOAD TABLE CONVENTIONAL | CF11_SHP_MON_SUM |      1 |        |      0 |00:00:00.01 |       2 |
|*  2 |   HASH JOIN              |                  |      1 |      1 |      0 |00:00:00.01 |       0 |
|   3 |    TABLE ACCESS FULL     | GT_SLE_MON       |      1 |      1 |      0 |00:00:00.01 |       0 |
|   4 |    TABLE ACCESS FULL     | CF01             |      0 |    109 |      0 |00:00:00.00 |       0 |
------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("I"."ITM_NO"="G"."ITM_NO")
Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

[딕셔너리]
  SELECT table_name, temporary, duration FROM user_tables WHERE table_name = 'GT_SLE_MON';
  GT_SLE_MON   Y   SYS$TRANSACTION

문제 ​

  1. ①은 531행을 넣었는데 ②가 0행을 읽은 이유를 DURATION과 원본의 커밋 위치로 설명하시오. 에러 없이 SUCCEEDED로 끝난 이유도 쓰시오.
  2. 같은 결과(11월 531행)를 내도록 프로시저를 고치시오. 임시 테이블을 ON COMMIT PRESERVE ROWS로 바꾸는 방법과 비교하고, 실패한 뒤 다시 돌릴 때 어디서부터 돌려야 하는지도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자