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

루프를 한 문장으로 바꾸자 12월 주문상품이 1,206건 모자란다

중급INSERT버거435 / 700

상황 ​

버거원 본사는 매달 초 지난달 주문상품을 DW 이관용 스테이징(DW_ORD_ITEM_M)으로 옮긴다. 지금 배치는 주문상품을 한 건씩 읽어 상품명과 상품 유형을 찾아 붙인 뒤 한 건씩 넣는데, 12월분에 1.6초가 걸렸다. 개발자가 INSERT … SELECT 한 문장으로 바꾸자 0.2초로 줄었다. 그런데 DW 쪽 건수 대조에서 1,206건이 모자랐다. 에러는 하나도 없었다.

테이블과 인덱스 ​

  • ORD_ITEM: 2,472,717행 · 55,265블록 · ORD_ITEM_X01 (ORD_DT, ORD_HMS) · 2013년 12월분 106,342행
  • ITEM: 92행 · 5블록 · PK ITEM_ID. 2013년 11월 단종 정리 때 상품 3개를 지웠다(지우기 전 95개). 그 3개가 일부 매장 POS에 남아 12월에도 1,206행이 팔렸다.
  • DW_ORD_ITEM_M(이 문제용 테이블): 한 달 치만 담는 스테이징이다. 배치를 시작할 때 비운다(TRUNCATE). 인덱스는 없고, DW가 배치가 끝난 뒤에 읽는다.
  • DW의 규칙은 "상품 마스터에 없는 상품은 상품명 (단종), 유형 ZZ로 받는다"이다. DB는 NOARCHIVELOG다.

원본 SQL ​

sql
-- 지금 배치 (1.6초)
DECLARE
  v_nm ITEM.ITEM_NM%TYPE;
  v_tp ITEM.ITEM_TYPE_CD%TYPE;
BEGIN
  EXECUTE IMMEDIATE 'TRUNCATE TABLE DW_ORD_ITEM_M';
  FOR r IN (SELECT ORD_NO, ITEM_ID, ORD_ITEM_QTY, ORD_DT
              FROM ORD_ITEM
             WHERE ORD_DT BETWEEN '20131201' AND '20131231') LOOP
    BEGIN
      SELECT ITEM_NM, ITEM_TYPE_CD INTO v_nm, v_tp
        FROM ITEM WHERE ITEM_ID = r.ITEM_ID;
    EXCEPTION
      WHEN NO_DATA_FOUND THEN v_nm := '(단종)'; v_tp := 'ZZ';
    END;
    INSERT INTO DW_ORD_ITEM_M (ORD_NO, ITEM_ID, ITEM_NM, ITEM_TYPE_CD, ORD_ITEM_QTY, ORD_DT)
    VALUES (r.ORD_NO, r.ITEM_ID, v_nm, v_tp, r.ORD_ITEM_QTY, r.ORD_DT);
  END LOOP;
  COMMIT;
END;

-- 개발자가 바꾼 판 (0.2초 · 1,206건 모자람)
INSERT INTO DW_ORD_ITEM_M (ORD_NO, ITEM_ID, ITEM_NM, ITEM_TYPE_CD, ORD_ITEM_QTY, ORD_DT)
SELECT i.ORD_NO, i.ITEM_ID, t.ITEM_NM, t.ITEM_TYPE_CD, i.ORD_ITEM_QTY, i.ORD_DT
  FROM ORD_ITEM i, ITEM t
 WHERE t.ITEM_ID = i.ITEM_ID
   AND i.ORD_DT BETWEEN '20131201' AND '20131231';

실행계획 학습용 예시 ​

xplan
[지금 배치 — 세션 통계]
  경과 시간                 1.58 ~ 1.66 초
  recursive calls           213,104
  execute count             212,698   (상품 SELECT INTO 106,342 · INSERT 106,342)
  session logical reads     약 330,400
  redo size                 약 30.2 MB
  적재 행                    106,342

[바꾼 판 — DISPLAY_CURSOR (ALLSTATS LAST)]
----------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                       |               |      1 |        |      0 |00:00:00.21 |    4930 |          |
|   1 |  LOAD TABLE CONVENTIONAL               | DW_ORD_ITEM_M |      1 |        |      0 |00:00:00.21 |    4930 |          |
|*  2 |   HASH JOIN                            |               |      1 |    106K|    105K|00:00:00.06 |    2688 | 1376K (0)|
|   3 |    TABLE ACCESS FULL                   | ITEM          |      1 |     92 |     92 |00:00:00.01 |       5 |          |
|   4 |    TABLE ACCESS BY INDEX ROWID BATCHED | ORD_ITEM      |      1 |    106K|    106K|00:00:00.03 |    2683 |          |
|*  5 |     INDEX RANGE SCAN                   | ORD_ITEM_X01  |      1 |    106K|    106K|00:00:00.01 |     381 |          |
----------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("T"."ITEM_ID"="I"."ITEM_ID")
   5 - access("I"."ORD_DT">='20131201' AND "I"."ORD_DT"<='20131231')

[DW 건수 대조]  원천 ORD_ITEM 12월 106,342 · 스테이징 105,136 · 차이 1,206 (지운 상품 3개)

문제 ​

  1. 바꾼 판에서 1,206건이 어디로 사라졌는지, 지금 배치에서는 그 행이 왜 남았는지 설명하시오.
  2. 지금 배치의 일량이 어디서 나오는지 세션 통계로 설명하시오.
  3. 빠르면서 결과도 지금 배치와 같은 한 문장으로 다시 작성하고, 끝났을 때 무엇을 확인할지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자