Skip to content
튜닝 연습PL/SQL과 사용자 정의 함수
PLSQL-022

POS 업체가 12월분을 통째로 다시 보내자 적재에 7초가 걸리고 redo가 91MB 쌓였다

실전PLSQL버거672 / 700

상황 ​

2013년 12월 26일부터 사흘 동안 POS 중계 서버 장애로 일부 매장의 주문상품이 본사에 들어오지 않았다. POS 업체는 어느 건이 빠졌는지 몰라 12월분 전체(103,046행)를 다시 보냈다. 본사 적재 프로그램은 한 행씩 넣고, 이미 있는 행은 예외로 잡아 넘긴다. 돌려 보니 7.2초가 걸렸고 redo가 91MB 쌓였는데, 새로 들어간 행은 4,312행뿐이었다. 오과장이 시험 DB에서 세 가지 안을 재 왔다. 장애가 나면 같은 재전송이 또 온다. 적재 규칙은 2019년부터 그대로다. "한 행이 실패해도 나머지는 넣는다. 실패한 행은 사유와 함께 남긴다. 이미 있는 행은 실패가 아니다."

테이블과 인덱스 ​

  • ORD_ITEM_RCV(POS 수신 테이블, 이 문제용): ORD_ITEM과 같은 컬럼 · 12월 재전송분 103,046행 · 2,301블록 · 인덱스 없음
  • ORD_ITEM 주문상품: 적재 전 2,468,405행(장애로 4,312행이 빠진 상태) · 적재 뒤 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID) 리프 6,870블록 · ORD_ITEM_X01 (ORD_DT, ORD_HMS)
  • 재전송분 103,046행 가운데 98,734행은 이미 ORD_ITEM에 있고, 4,312행이 빠져 있었다.
  • ORD_ITEM_REJ(반송함): ORD_NO · ITEM_ID · ERR_CD · ERR_MSG · 이번에는 0행
  • 재전송은 야간에 한 세션만 적재한다. 낮에는 POS가 같은 테이블에 실시간으로 넣는다.

원본 SQL ​

sql
PROCEDURE LOAD_POS_RESEND IS
  v_ok   PLS_INTEGER := 0;
  v_dup  PLS_INTEGER := 0;
  v_cd   PLS_INTEGER;
  v_msg  VARCHAR2(200);
BEGIN
  FOR r IN (SELECT * FROM ORD_ITEM_RCV) LOOP
    BEGIN
      INSERT INTO ORD_ITEM VALUES r;
      v_ok := v_ok + 1;
    EXCEPTION
      WHEN DUP_VAL_ON_INDEX THEN
        v_dup := v_dup + 1;                                -- 이미 있음
      WHEN OTHERS THEN
        v_cd  := SQLCODE;
        v_msg := SUBSTR(SQLERRM, 1, 200);
        INSERT INTO ORD_ITEM_REJ (ORD_NO, ITEM_ID, ERR_CD, ERR_MSG)
        VALUES (r.ORD_NO, r.ITEM_ID, v_cd, v_msg);
    END;
  END LOOP;
  COMMIT;
END;

실행계획 학습용 예시 ​

xplan
[지금 판 — 12월 재전송분 103,046행 (V$MYSTAT 증가분)]
  경과 시간                 7.2 초
  recursive calls           104,129
  execute count             103,072
  session logical reads     792,416
  redo size                 91.3 MB
  undo change vector size   27.6 MB
  결과                      신규 4,312 · 이미 있음 98,734 · 반송 0

[TKPROF — 루프 안의 INSERT]
INSERT INTO ORD_ITEM VALUES (:B1 , :B2 , :B3 , …)

call     count       cpu    elapsed       disk      query    current        rows
------- -------  -------- ---------- ---------- ---------- ----------  ----------
Execute  103046      4.61       4.83          0       3118     781944        4312
(103,046번 가운데 98,734번은 ORA-00001로 실패했고, 실패할 때마다 넣던 행을 되돌렸다)

[오과장의 세 안 — 같은 재전송분 · 시험 DB]
  안                                                                  경과    recursive calls   논리 읽기    redo       결과
  ① BULK 1000 + FORALL … SAVE EXCEPTIONS                              5.6 초          251       786,902    90.8 MB   같음 · 예외 98,734건을 BULK_EXCEPTIONS로 받음
  ② INSERT … SELECT … LOG ERRORS REJECT LIMIT UNLIMITED               6.8 초           38     1,108,551   128.4 MB   같음 · 오류 기록표에 98,734행
  ③ INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(ORD_ITEM, ORD_ITEM_PK) */
       … SELECT                                                       5.9 초           31       790,377    90.1 MB   같음 · 이미 있음 건수는 따로 세야 함

문제 ​

  1. 지금 판의 일량이 어디서 나오는지 TKPROF와 세션 통계로 설명하시오. ①로 바꾸면 recursive calls는 크게 주는데 시간과 redo는 왜 거의 그대로인지도 쓰시오.
  2. 세 안을 비교하고, 적재 규칙을 지키면서 일량을 줄이는 적재 프로그램을 작성하시오.
  3. 고친 적재의 전제 조건과 부작용을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자