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

인수한 매장 12곳의 주문을 옮기는 데 5.7초, 올해 세 번 더 해야 하는데 안이 네 개 올라왔다

실전PLSQL버거698 / 700

상황 ​

버거 본사가 다른 브랜드의 매장 12곳을 인수했다. 그 매장들의 2013년 주문은 옛 POS 번호 체계라서, 이관팀이 스테이징 테이블에 받아 두고 본사 ORD와 ORD_ITEM으로 옮긴다. 새 주문 번호는 본사 POS와 같은 시퀀스 SQ_ORD_NO로 매긴다. 리허설에서 이관 프로그램이 5.7초 걸렸다. 올해 인수가 세 번 더 있고 다음 인수는 매장 수가 네 배다. 오과장이 시험 DB에서 안 네 개를 재 왔는데, D는 가장 빨랐지만 결과 건수가 이상했다. 박팀장: "하나를 골라 프로그램을 써 오고, 고른 안의 전제와 부작용까지 적어 줘요."

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록 · ORD_PK (ORD_NO) · ORD_X01 (ORD_DT, ORD_HMS) · ORD_NO는 시퀀스 SQ_ORD_NO(CACHE 20)로 매긴다 · 트리거 없음
  • ORD_ITEM 주문상품: 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID) · ORD_ITEM_X01 (ORD_DT, ORD_HMS)
  • STG_ORD 인수 매장 주문(이 문제용): 41,200행 · OLD_ORD_KEY VARCHAR2(20)(옛 POS 번호) · ORD_DT · ORD_HMS · SHOP_NO · PK (OLD_ORD_KEY)
  • STG_ORD_ITEM 인수 매장 주문상품(이 문제용): 102,957행 · OLD_ORD_KEY · ITEM_ID · ORD_ITEM_QTY · PK (OLD_ORD_KEY, ITEM_ID)
  • 스테이징 두 테이블은 이관팀 것이라 칸을 더해도 된다. 이관은 POS가 닫힌 02:00 ~ 05:00에 돌고, 인수 한 번은 한 트랜잭션으로 옮긴다(실패하면 전부 되돌린다).

원본 SQL ​

sql
PROCEDURE P_MIG_ACQ IS
  v_no ORD.ORD_NO%TYPE;
BEGIN
  FOR o IN (SELECT OLD_ORD_KEY, ORD_DT, ORD_HMS, SHOP_NO FROM STG_ORD) LOOP
    SELECT SQ_ORD_NO.NEXTVAL INTO v_no FROM dual;                  -- 주문마다 새 번호
    INSERT INTO ORD (ORD_NO, ORD_DT, ORD_HMS, SHOP_NO)
    VALUES (v_no, o.ORD_DT, o.ORD_HMS, o.SHOP_NO);
    FOR i IN (SELECT ITEM_ID, ORD_ITEM_QTY
                FROM STG_ORD_ITEM
               WHERE OLD_ORD_KEY = o.OLD_ORD_KEY) LOOP
      INSERT INTO ORD_ITEM (ORD_NO, ITEM_ID, ORD_ITEM_QTY, ORD_DT, ORD_HMS)
      VALUES (v_no, i.ITEM_ID, i.ORD_ITEM_QTY, o.ORD_DT, o.ORD_HMS);
    END LOOP;
  END LOOP;
  COMMIT;
END;

실행계획 학습용 예시 ​

xplan
[세션 통계 — 리허설 (주문 41,200 · 주문상품 102,957)]
  경과 5.7 초 · recursive calls 311,437 · session logical reads 878,412 · redo size 71 MB

[TKPROF — 루프 안의 문장 (dep=1)]
                                                       call      count    query    current      rows
SELECT SQ_ORD_NO.NEXTVAL FROM DUAL                     Execute   41,200        0          0         0
                                                       Fetch     41,200        0          0    41,200
INSERT INTO ORD (…) VALUES (:B4, :B3, :B2, :B1)        Execute   41,200    1,214    170,388    41,200
SELECT ITEM_ID, ORD_ITEM_QTY FROM STG_ORD_ITEM         Execute   41,200        0          0         0
  WHERE OLD_ORD_KEY = :B1                              Fetch     41,200  164,800          0   102,957
INSERT INTO ORD_ITEM (…) VALUES (:B5, …)               Execute  102,957    3,018    521,646   102,957
update seq$ … (dep=2)                                  Execute    2,060    2,060      4,120     2,060

[오과장의 네 안 — 시험 DB · 같은 41,200건]
  안                                                         경과    recursive calls   redo     결과 (ORD / ORD_ITEM)
  A 지금 판 (주문마다 NEXTVAL, 줄마다 INSERT)                   5.7 초        311,437     71 MB    41,200 / 102,957
  B FORALL INSERT … RETURNING BULK COLLECT (1,000건씩)          0.9 초          2,247     52 MB    41,200 / 102,957
  C 스테이징에 새 번호를 먼저 매기고 INSERT … SELECT 두 문장      0.6 초          2,151     58 MB    41,200 / 102,957
  D INSERT ALL (STG_ORD ⋈ STG_ORD_ITEM이 원천, NEXTVAL과 CURRVAL) 0.5 초          5,231     67 MB   102,957 / 102,957

[D의 문장]
  INSERT ALL
    INTO ORD      (ORD_NO, ORD_DT, ORD_HMS, SHOP_NO)                 VALUES (SQ_ORD_NO.NEXTVAL, ORD_DT, ORD_HMS, SHOP_NO)
    INTO ORD_ITEM (ORD_NO, ITEM_ID, ORD_ITEM_QTY, ORD_DT, ORD_HMS)  VALUES (SQ_ORD_NO.CURRVAL, ITEM_ID, ORD_ITEM_QTY, ORD_DT, ORD_HMS)
  SELECT o.ORD_DT, o.ORD_HMS, o.SHOP_NO, i.ITEM_ID, i.ORD_ITEM_QTY
    FROM STG_ORD o, STG_ORD_ITEM i
   WHERE i.OLD_ORD_KEY = o.OLD_ORD_KEY;
  → 205,914 rows created (에러 없음)

문제 ​

  1. 지금 판의 일량이 어디서 나오는지 TKPROF로 설명하시오.
  2. 네 안을 비교해 하나를 고르고 이관 프로그램을 작성하시오. D가 에러 없이 끝났는데 왜 틀렸는지도 쓰시오.
  3. 고른 안의 전제 조건과 부작용을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자