Skip to content
튜닝 연습Lock과 동시성 제어
LOCK-003

1,000건마다 커밋했는데 ORA-01555로 죽었다

중급PLSQL유통498 / 700

상황 ​

본사 레거시 유통에서 3월 1일 적용 판매가(CF05) 일부가 잘못 등록돼 있어 정정했고, 이어서 판매상품(CF11)의 판매 단가를 정정 단가로 다시 맞추는 배치를 돌렸다. 평소라면 새벽에 돌릴 일을 매장 정산 마감 때문에 오전 10:00에 돌렸다. 매장 POS는 영업 중이었다. 10:03:52에 배치가 ORA-01555로 죽었다. 본사 전산 담당자가 물었다. "언두가 모자라지 않게 1,000건마다 커밋했는데요. 그냥 다시 돌리면 되나요?"

테이블과 인덱스 ​

  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · 2023-03-01 이후 판매분 약 46만 행 · 영업 중에는 POS가 반품이나 수량 정정으로 계속 UPDATE하고 COMMIT한다
  • CF10 판매: 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · SLE_DT VARCHAR2(8)
  • CF05 상품가격: 157행 · 5블록 · PK (ITM_NO, UPRC_KN_CD, APL_DT) · 정정한 3월 1일 판매가(UPRC_KN_CD = '01')는 109개 상품 모두 있다 · 그 뒤 판매가 변경은 없다
  • 언두 테이블스페이스 200MB 고정(AUTOEXTEND OFF, 설치 때 그대로) · undo_retention 900

원본 SQL ​

sql
DECLARE
  v_uprc CF05.ITM_UPRC%TYPE;
  n      PLS_INTEGER := 0;
BEGIN
  FOR r IN (SELECT d.ROWID AS rid, d.ITM_NO
              FROM CF10 h, CF11 d
             WHERE d.SLE_NO = h.SLE_NO
               AND h.SLE_DT >= '20230301') LOOP
    SELECT ITM_UPRC INTO v_uprc
      FROM CF05
     WHERE ITM_NO = r.ITM_NO AND UPRC_KN_CD = '01' AND APL_DT = '20230301';

    UPDATE CF11 SET SLE_UPRC = v_uprc WHERE ROWID = r.rid;

    n := n + 1;
    IF MOD(n, 1000) = 0 THEN COMMIT; END IF;     -- 언두가 모자라지 않게 (본사 전산 규칙)
  END LOOP;
  COMMIT;
END;

실행계획 학습용 예시 ​

xplan
[시간표 — 오전 · 매장 영업 중]
시각        세션 A — 단가 정정 배치 (SID 131)                          세션 B · C — 매장 POS (SID 47 · 62)
10:00:00    OPEN 커서 — 읽기 시점(SCN)이 여기서 고정된다
            CF10 ⋈ CF11 을 판매번호 순으로 흘려 읽는다 (정렬 없음)
10:00:01 ~  FETCH → 단가 조회 → UPDATE CF11 → 1,000 건마다 COMMIT          반품 · 수량 정정 UPDATE CF11 → COMMIT (분당 수십 건)
            (COMMIT 마다 그 1,000 건의 언두가 '덮어써도 되는' 칸이 된다)
10:02 ~     커서가 OPEN 뒤에 바뀐 블록에 닿을 때마다 언두로 10:00 의 과거 사본(CR)을 만들어 읽는다
10:03:52    FETCH 287,001 번째 → ORA-01555: snapshot too old: rollback segment number 7 with name "_SYSSMU7_2180427191$" too small
            → 배치 종료. 287,000 건은 이미 COMMIT · 나머지 약 17만 건은 옛 단가 그대로
(B · C 는 기다리지 않는다 — 블로킹이 아니라 '과거가 사라진' 장애다)

[알림 로그]
  ORA-01555 caused by SQL statement below (SQL ID: 9xw2k4bq1m7dc, Query Duration=232 sec, SCN: 0x0000000000a3c1f2):
  SELECT D.ROWID AS RID, D.ITM_NO FROM CF10 H, CF11 D WHERE D.SLE_NO = H.SLE_NO AND H.SLE_DT >= '20230301'

[V$UNDOSTAT — 10:00 ~ 10:10 구간]
  UNDOBLKS   TXNCOUNT   MAXQUERYLEN   TUNED_UNDORETENTION   UNXPSTEALCNT   SSOLDERRCNT   NOSPACEERRCNT
    24,310      1,962           232                   418             37             1               0

문제 ​

  1. 시간표와 알림 로그, V$UNDOSTAT을 근거로 무엇이 '너무 오래되어' 없어졌는지, 1,000건마다 한 커밋이 왜 이를 막지 못했는지(오히려 거들었는지) 설명하시오.
  2. 배치를 고치시오. "커밋을 5,000건마다로 늘리자", "언두를 늘리자"는 의견에도 답하시오.
  3. 지금 287,000건이 고쳐진 채 남아 있다. 다시 돌려도 되는지, 고친 판이 중간에 죽으면 어떻게 되는지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자