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

보관 배치에 APPEND를 붙였는데 redo가 그대로이고, 트리거를 끄자 보관 일시가 비었다

중급INSERT한빛유통461 / 700

상황 ​

매년 1월 첫 일요일에 지난해 주문을 보관 테이블로 옮기는 배치가 돈다. 주문(T_ORD_ARC)을 먼저 옮기고 주문상세(T_ORD_DET_ARC)를 옮긴다. 오과장이 두 INSERT에 모두 /*+ APPEND */를 붙였는데, 주문상세 단계의 redo와 시간은 그대로였다. 리허설에서 실행계획의 Note를 보고 보관 일시를 채우던 트리거를 꺼 봤더니 계획은 여전히 그대로였고, 그렇게 들어간 행은 ARC_DT가 NULL이었다.

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록 · ORD_DT 2023-01-01 ~ 2024-12-31 · 2023년 주문 50,060건 · PK_T_ORD (ORD_SEQ)
  • T_ORD_DET: 200,000행 · 896블록(전체 스캔 807) · 2023년 주문의 상세 100,140행 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO)
  • T_ORD_ARC · T_ORD_DET_ARC 보관 테이블(문제 설정): 원본 컬럼에 ARC_DT DATE와 ARC_USER VARCHAR2(30)를 더했다. 처음 보관하는 해라 둘 다 비어 있다. T_ORD_ARC에는 PK_T_ORD_ARC (ORD_SEQ)만 있다.
  • T_ORD_DET_ARC에는 PK_T_ORD_DET_ARC (ORD_SEQ, ORD_DET_NO)와 FK_ORD_DET_ARC (ORD_SEQ) REFERENCES T_ORD_ARC가 있고, 행 트리거 TRG_ORD_DET_ARC_BI(BEFORE INSERT FOR EACH ROW)가 :NEW.ARC_DT := SYSDATE; :NEW.ARC_USER := USER;를 채운다.
  • 보관 테이블은 이 배치만 쓰고, 보관 담당자가 가끔 조회만 한다. 연습 DB 복사본이다(NOARCHIVELOG).

원본 SQL ​

sql
-- 매년 1월 첫 일요일 · 지난해 주문 보관
-- ① 주문
INSERT /*+ APPEND */ INTO T_ORD_ARC
SELECT o.*, SYSDATE, USER
  FROM T_ORD o
 WHERE o.ORD_DT >= DATE '2023-01-01' AND o.ORD_DT < DATE '2024-01-01';
COMMIT;

-- ② 주문상세 (ARC_DT · ARC_USER 는 트리거가 채운다)
INSERT /*+ APPEND */ INTO T_ORD_DET_ARC (ORD_SEQ, ORD_DET_NO, ITM_ID, ORD_QTY, UNT_PRC)
SELECT d.ORD_SEQ, d.ORD_DET_NO, d.ITM_ID, d.ORD_QTY, d.UNT_PRC
  FROM T_ORD_DET d
 WHERE d.ORD_SEQ IN (SELECT ORD_SEQ FROM T_ORD_ARC);
COMMIT;

실행계획 학습용 예시 · 교재 14화 §4.3 모양 ​

xplan
[② 주문상세 보관 — 리허설 1회차]
-----------------------------------------------------------------------------------------------------
| Id  | Operation                | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |               |      1 |        |      0 |00:00:05.87 |    623K |
|   1 |  LOAD TABLE CONVENTIONAL | T_ORD_DET_ARC |      1 |        |      0 |00:00:05.87 |    623K |
|*  2 |   HASH JOIN RIGHT SEMI   |               |      1 |    100K|    100K|00:00:00.12 |     919 |
|   3 |    INDEX FAST FULL SCAN  | PK_T_ORD_ARC  |      1 |  50060 |  50060 |00:00:00.01 |     112 |
|   4 |    TABLE ACCESS FULL     | T_ORD_DET     |      1 |    200K|    200K|00:00:00.03 |     807 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("D"."ORD_SEQ"="ORD_SEQ")

Note
-----
   - Direct Load disabled because triggers are defined

[② 같은 문장 — 리허설 2회차 · ALTER TRIGGER TRG_ORD_DET_ARC_BI DISABLE 뒤]
  1번 줄  LOAD TABLE CONVENTIONAL  T_ORD_DET_ARC  · A-Time 00:00:03.62 · Buffers 512K (아래 줄은 1회차와 같다)
Note
-----
   - Direct Load disabled because parent referential constraints are present

[세션 통계 — ② 한 번]
                            1회차 (트리거 켬)    2회차 (트리거 끔)    (비교) ① 주문 보관
  redo size                    42.7 MB             27.9 MB              0.1 MB
  undo change vector size      12.4 MB              8.2 MB              0.02 MB
  session logical reads        623,410             512,880              1,980
  경과                          5.9 초               3.6 초              0.08 초
  ARC_DT가 NULL인 행                 0             100,140              —

문제 ​

  1. ①은 직접 경로로 돌았는데 ②는 왜 일반 INSERT로 돌았는지, 2회차에서 트리거를 끈 뒤에도 그대로였던 이유와 함께 설명하시오.
  2. ②가 직접 경로로 돌도록 고치시오. 트리거가 하던 일과 참조 무결성을 잃지 않게 하시오.
  3. 고친 배치가 정말 직접 경로로 돌았는지 확인하는 방법과, 제약을 다시 켤 때의 선택지를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자