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

이번 달 주문상품 칸을 밤마다 지우고 다시 넣는 데 2분이 넘고 redo가 110MB씩 나온다

실전DDL버거467 / 700

상황 ​

버거원 본사 분석 DB에는 주문상품을 월별로 나눈 ORD_ITEM_RANGE가 있다. 매장 정정(취소, 수량 수정)이 그달 안에서 계속 들어오므로, 매일 03:00에 이번 달 칸을 지우고 운영 ORD_ITEM에서 다시 넣는다. 12월 말로 가면서 이 단계가 2분 10초까지 늘었고, 아카이브 로그는 이 단계에서만 하루 110MB가 나온다. 오과장이 두 가지 안을 들고 왔다. ⓐ 칸을 TRUNCATE하고 /*+ APPEND */로 넣는다. ⓑ 칸을 TRUNCATE하고 그 칸의 로컬 인덱스만 끈 채 일반 INSERT로 넣은 뒤 다시 짓는다. 06:00에는 매장 집계 리포트가 이번 달 칸을 통째로 읽는다.

테이블과 인덱스 ​

  • ORD_ITEM_RANGE(문제 설정, ORD_ITEM의 월별 파티션 복사본): PARTITION BY RANGE (ORD_YM) 2012년 1월 ~ 2013년 12월 24칸과 빈 P_MAX. 칸 하나에 약 103,000행 · 데이터 약 2,300블록이다. 오늘(12월 30일) 밤 다시 넣을 P201312은 99,720행이다.
  • LOCAL 인덱스 ORD_ITEM_RANGE_X01 (ORD_DT, ORD_HMS) · ORD_ITEM_RANGE_X02 (ITEM_ID, ORD_DT). 둘 다 유니크가 아니다. GLOBAL 인덱스, FK, 트리거는 없다.
  • 이 버전은 파티션의 첫 익스텐트를 8MB(1,024블록)로 잡는다(교재 14화 §8.5). 테이블에 STORAGE 절은 없다.
  • 원천 ORD_ITEM: 2,472,717행 · 55,265블록 · ORD_ITEM_X01 (ORD_DT, ORD_HMS)
  • 분석 DB는 ARCHIVELOG이고 테이블과 인덱스는 LOGGING이다. 03:00 ~ 06:00에는 이 테이블에 다른 작업이 없다.

원본 SQL ​

sql
-- 매일 03:00 · 이번 달 칸 다시 채우기 (:v_ym = '201312')
DELETE FROM ORD_ITEM_RANGE
 WHERE ORD_YM = :v_ym;

INSERT INTO ORD_ITEM_RANGE (ORD_YM, ORD_NO, ITEM_ID, ORD_ITEM_QTY, ORD_DT, ORD_HMS)
SELECT SUBSTR(ORD_DT, 1, 6), ORD_NO, ITEM_ID, ORD_ITEM_QTY, ORD_DT, ORD_HMS
  FROM ORD_ITEM
 WHERE ORD_DT BETWEEN :v_ym || '01' AND :v_ym || '31';
COMMIT;

실행계획 학습용 예시 · 빈 블록 수는 교재 14화 §8.5 실측 ​

xplan
[① DELETE]
---------------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT        |                |      1 |        |      0 |00:00:47.21 |    675K |       |       |
|   1 |  DELETE                 | ORD_ITEM_RANGE |      1 |        |      0 |00:00:47.21 |    675K |       |       |
|   2 |   PARTITION RANGE SINGLE|                |      1 |  96412 |  96412 |00:00:00.03 |    2244 |   KEY |   KEY |
|*  3 |    TABLE ACCESS FULL    | ORD_ITEM_RANGE |      1 |  96412 |  96412 |00:00:00.02 |    2244 |   KEY |   KEY |
---------------------------------------------------------------------------------------------------------------------
   3 - filter("ORD_YM"=:V_YM)

[② INSERT]
-------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                    |                |      1 |        |      0 |00:01:23.10 |    627K |
|   1 |  LOAD TABLE CONVENTIONAL            | ORD_ITEM_RANGE |      1 |        |      0 |00:01:23.10 |    627K |
|   2 |   TABLE ACCESS BY INDEX ROWID BATCHED| ORD_ITEM      |      1 |    102K|  99720 |00:00:00.09 |    2643 |
|*  3 |    INDEX RANGE SCAN                 | ORD_ITEM_X01   |      1 |    102K|  99720 |00:00:00.02 |     398 |
-------------------------------------------------------------------------------------------------------------
   3 - access("ORD_DT">=:V_YM||'01' AND "ORD_DT"<=:V_YM||'31')

[세션 통계 — ① + ② + COMMIT (분석 DB · ARCHIVELOG)]
  redo size                  약 110 MB (DELETE 약 44 · INSERT 약 66)
  undo change vector size    약 39 MB
  db block changes           약 1,130,000
  경과                       2분 10초

[P201312 칸 — USER_SEGMENTS · DBMS_SPACE (작업 뒤)]
  테이블 칸 24 MB 할당 · 고수위선 아래 2,246블록
  X01 칸 리프 약 410블록 · X02 칸 리프 약 330블록

문제 ​

  1. 지금 방식의 비용(redo 110MB, 2분 10초)이 어디서 나오는지 행마다 하는 일로 설명하시오.
  2. 칸 단위 DDL을 써서 이 단계를 다시 작성하시오. 로컬 인덱스를 어떻게 다룰지도 SQL로 쓰시오.
  3. 오과장의 ⓐ와 ⓑ를 redo, 06:00 리포트가 읽는 블록, 잠금, 실패했을 때의 상태로 비교하고 하나를 고르시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자