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

2022년 주문을 5,000건씩 보관 DB로 옮긴 작업은 '완료'인데 보관본이 27,816건 모자란다

중급INSERT한빛유통490 / 700

상황 ​

한빛유통은 2022년 주문을 보관 DB로 옮긴 뒤 운영에서 지우기로 했다. DB 링크가 가끔 끊기므로 개발자는 5,000건씩 옮기고, 어디까지 옮겼는지를 체크포인트 테이블에 적어 끊기면 그다음부터 다시 돌게 했다. 일요일 밤 작업은 62회차 만에 '완료'로 끝났다. 다음 단계(운영 원본 삭제)는 건수 대조에서 멈췄다. 원본은 333,245건인데 보관본은 305,429건이었다. 박팀장: "에러도 없고 링크도 한 번도 안 끊겼다는데, 2만 8천 건이 어디로 갔습니까?"

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT는 2022-01-01 ~ 2024-12-31이고 전부 자정이다(시분초 없음, 하루 약 913건) · 2022년 333,245건 · ORD_SEQ는 값이 유일하다(인덱스 없음)
  • 인덱스 IX_ORD_BIG_DT_AMT (ORD_DT, ORD_AMT)
  • T_ORD_ARC@ARC: 보관 DB의 보관 테이블(같은 컬럼). 작업 전에는 비어 있었고, 보관 DB를 쓰는 다른 작업은 없다.
  • T_MIG_CKPT (JOB_NM, LAST_DT): 체크포인트 테이블(이 문제용). 작업 시작 전 LAST_DT는 2021-12-31이었다.

원본 SQL ​

sql
-- 일요일 22:00 · 2022년 주문 보관 이관 (끊기면 같은 블록을 다시 실행하면 이어서 돈다)
DECLARE
  v_last DATE;
  v_rows NUMBER;
BEGIN
  LOOP
    SELECT LAST_DT INTO v_last FROM T_MIG_CKPT WHERE JOB_NM = 'ORD_ARC_2022';

    INSERT INTO T_ORD_ARC@ARC (ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, ORD_AMT)
    SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, ORD_AMT
      FROM (SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, ORD_AMT
              FROM T_ORD_BIG
             WHERE ORD_DT > v_last
               AND ORD_DT < DATE '2023-01-01'
             ORDER BY ORD_DT)
     WHERE ROWNUM <= 5000;
    v_rows := SQL%ROWCOUNT;
    EXIT WHEN v_rows = 0;

    UPDATE T_MIG_CKPT                                   -- 이번에 옮긴 마지막 주문일
       SET LAST_DT = (SELECT MAX(ORD_DT) FROM T_ORD_ARC@ARC)
     WHERE JOB_NM = 'ORD_ARC_2022';
    COMMIT;
  END LOOP;
END;

실행계획 학습용 예시 ​

xplan
[회차별 기록 — 체크포인트와 넣은 행]
  회차   넣은 행   이번 회차의 마지막 ORD_DT (LAST_DT)
    1     5,000    2022-01-06
    2     5,000    2022-01-12
    3     5,000    2022-01-17
    …       …      …
   61     5,000    2022-12-26
   62       429    2022-12-31
   63         0    → 끝 · 스케줄러 상태 SUCCEEDED · 링크 끊김 0번

[대조 — 원본 삭제 단계가 멈춘 이유]
  원본 2022년 333,245 · 보관본 305,429 · 차이 27,816 → ORA-20010 (삭제 단계 대조 불일치)
  하루 치가 일부만 들어간 날 61일 · 예) 2022-01-06 원본 911행, 보관본 433행

[회차 하나의 SELECT — DISPLAY_CURSOR(FORMAT => 'ALLSTATS LAST')]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                   |      1 |        |   5000 |00:00:00.04 |    5031 |
|*  1 |  COUNT STOPKEY                 |                   |      1 |        |   5000 |00:00:00.04 |    5031 |
|   2 |   VIEW                         |                   |      1 |   5000 |   5000 |00:00:00.04 |    5031 |
|   3 |    TABLE ACCESS BY INDEX ROWID | T_ORD_BIG         |      1 |   5000 |   5000 |00:00:00.03 |    5031 |
|*  4 |     INDEX RANGE SCAN           | IX_ORD_BIG_DT_AMT |      1 |   5000 |   5000 |00:00:00.01 |      18 |
------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM<=5000)
   4 - access("ORD_DT">:B1 AND "ORD_DT"<TO_DATE(' 2023-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))

문제 ​

  1. 링크가 한 번도 끊기지 않았는데 27,816건이 빠진 이유를 회차별 기록과 ORD_DT의 분포로 설명하시오.
  2. 빠짐없이 옮기고, 끊긴 뒤 다시 돌려도 빠지거나 겹치지 않도록 이관을 다시 작성하시오. 체크포인트에 무엇을 적어야 하는지도 쓰시오.
  3. 이번 사고에서 원본을 지키는 데 실제로 효과가 있었던 장치와, 고친 판에서도 남겨야 할 확인을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자