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

직접 경로로 바꿨다는 컨설팅 사본 적재가 5분을 넘기고, 사본은 10만 블록으로 불었다

실전PLSQL버거439 / 700

상황 ​

버거원 본사는 매달 1일 03:00에 지난달 주문상품을 가맹 컨설팅팀용 사본 CNS_ORD_ITEM에 다시 채운다(매장, 날짜, 시간대, 메뉴, 수량). 몇 년 전에 만든 프로시저는 루프 안의 INSERT에 /*+ APPEND */를 붙여 두었고, 4초 남짓에 끝났다. 지난주 조민우가 실행계획을 보다가 그 INSERT가 직접 경로로 돌지 않는다는 것을 알고 APPEND_VALUES로 바꿨다. 리허설에서 둘째 행에 ORA-12838이 나자 INSERT마다 커밋을 넣었다. 이번에는 끝까지 돌았지만 5분 7초가 걸렸고, 컨설팅 리포트(사본 전체 스캔)가 약 420블록에서 104,780블록을 읽게 됐다. 조민우: "직접 경로가 더 빠르다고 해서 바꾼 건데요."

테이블과 인덱스 ​

  • ORD: 1,000,000행 · 22,422블록 · ORD_X01 (ORD_DT, ORD_HMS) · 2013년 12월 주문 42,408건(리프 약 140블록, 테이블 951블록)
  • ORD_ITEM: 2,472,717행 · 55,265블록 · ORD_ITEM_X01 (ORD_DT, ORD_HMS) · 2013년 12월 주문상품 104,750행(리프 약 390블록, 테이블 약 2,340블록)
  • CNS_ORD_ITEM(이 문제용): SHOP_NO · ORD_DT · HR_BAND('M' 오전 · 'L' 점심 · 'A' 오후 · 'D' 저녁) · ITEM_ID · QTY · 인덱스, 트리거, FK 없음 · 한 달 치를 일반 INSERT로 넣으면 약 420블록(블록당 약 250행)이다. 이 배치만 쓰고, 낮에는 컨설팅 리포트가 읽기만 한다.
  • 본사 분석 DB(Oracle 19c, NOARCHIVELOG)다. 배치가 도는 03:00에는 다른 세션이 이 사본을 쓰지 않는다.

원본 SQL ​

sql
-- 처음 판 (몇 년 전) · 4초 남짓
PROCEDURE P_CNS_LOAD(p_ym VARCHAR2) IS                       -- p_ym = '201312'
  v_band VARCHAR2(1);
BEGIN
  EXECUTE IMMEDIATE 'TRUNCATE TABLE CNS_ORD_ITEM';
  FOR r IN (SELECT o.SHOP_NO, i.ORD_DT, i.ORD_HMS, i.ITEM_ID, i.ORD_ITEM_QTY
              FROM ORD o, ORD_ITEM i
             WHERE o.ORD_DT BETWEEN p_ym || '01' AND p_ym || '31'
               AND i.ORD_DT BETWEEN p_ym || '01' AND p_ym || '31'
               AND i.ORD_NO = o.ORD_NO) LOOP
    v_band := CASE WHEN r.ORD_HMS < '110000' THEN 'M'
                   WHEN r.ORD_HMS < '140000' THEN 'L'
                   WHEN r.ORD_HMS < '170000' THEN 'A'
                   ELSE 'D' END;
    INSERT /*+ APPEND */ INTO CNS_ORD_ITEM (SHOP_NO, ORD_DT, HR_BAND, ITEM_ID, QTY)
    VALUES (r.SHOP_NO, r.ORD_DT, v_band, r.ITEM_ID, r.ORD_ITEM_QTY);
  END LOOP;
  COMMIT;
END;

-- 지난주 바꾼 판 (조민우) · 루프 안의 INSERT 만 다르다
    INSERT /*+ APPEND_VALUES */ INTO CNS_ORD_ITEM (SHOP_NO, ORD_DT, HR_BAND, ITEM_ID, QTY)
    VALUES (r.SHOP_NO, r.ORD_DT, v_band, r.ITEM_ID, r.ORD_ITEM_QTY);
    COMMIT;                                                  -- 둘째 행의 ORA-12838 을 피하려고 넣었다

실행계획 학습용 예시 ​

xplan
[처음 판 — 12월분 104,750행 · 세션 통계]
  경과 시간                    3.9 초
  INSERT 실행 횟수             104,750
  user commits                 2   (TRUNCATE 의 암시적 커밋 + 마지막 COMMIT)
  redo size                    약 27.4 MB
  undo change vector size      약 6.9 MB
  루프 안 INSERT 의 실행계획    INSERT STATEMENT → LOAD TABLE CONVENTIONAL  CNS_ORD_ITEM
  적재 뒤 CNS_ORD_ITEM          약 420블록

[바꾼 판 ① — APPEND_VALUES · 커밋 없이 (리허설)]
  첫 행   1 row created   (실행계획 LOAD AS SELECT  CNS_ORD_ITEM)
  둘째 행 ORA-12838: cannot read/modify an object after modifying it in parallel
  → 롤백 · 0행

[바꾼 판 ② — APPEND_VALUES + INSERT 마다 COMMIT (리허설) · 세션 통계]
  경과 시간                    5분 7초
  INSERT 실행 횟수             104,750
  user commits                 104,752   (행마다 104,750 + TRUNCATE + 마지막 COMMIT)
  log file sync 대기           약 2분 41초
  physical writes direct       104,750
  redo size                    약 112 MB   (적재마다 고수위선과 공간 비트맵을 고친 기록 · 커밋 기록)
  CNS_ORD_ITEM (USER_SEGMENTS) 104,780블록 · 행 104,750
  컨설팅 리포트 (CNS_ORD_ITEM 전체 스캔)  Buffers 약 420 → 104,780

문제 ​

  1. 처음 판의 /*+ APPEND */가 직접 경로로 돌지 않은 이유와, 바꾼 판 ①이 둘째 행에서 ORA-12838로 멈춘 이유를 설명하시오.
  2. 바꾼 판 ②가 5분 넘게 걸리고 사본이 104,780블록이 된 이유를 직접 경로가 블록을 쓰는 방식으로 설명하시오.
  3. (가) INSERT /*+ APPEND */ … SELECT 한 문장, (나) BULK COLLECT와 FORALL … APPEND_VALUES, (다) FORALL 일반 INSERT를 실행 횟수, 커밋, 블록 수, redo, 잠금으로 비교하고 고른 방법으로 다시 작성하시오. 이미 커진 사본을 되돌리는 방법도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자