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

실패해서 되돌렸다는 재고 마감을 세 번 돌리자 재고가 하루 판매량의 세 배만큼 줄었다

기초PLSQL유통536 / 700

상황 ​

본사 레거시 유통의 「일 재고 마감」 프로시저는 매일 23:30에 그날 판매 수량만큼 매장 재고를 빼고, 날짜별 백업 테이블을 떠 두고, 리포트용 재고표를 채운다. 운영 문서에는 "실패하면 예외 처리부에서 ROLLBACK하므로 처음 상태로 돌아간다"라고 적혀 있다. 12월 30일 밤 마감이 실패했다. 다음 날 아침 운영자가 원인(분류가 비어 있는 신상품)을 고치고 다시 돌렸는데 또 실패했고, 백업 테이블을 지운 뒤 세 번째로 돌려 성공했다. 그날 오후 매장 여섯 곳에서 전화가 왔다. "전산 재고가 실제보다 한참 적어요."

테이블과 인덱스 ​

  • CF18 일별상품재고: 408행 · 6블록 · PK (SHP_NO, ITM_NO, INV_BS_DT) · 매장과 상품마다 그날 재고 한 행
  • CF10 판매: 500,000행 · 4,583블록 · SLE_DT VARCHAR2(8) · SHP_NO / CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO)
  • CF01 상품: 109행 · 12월 30일에 CTG_NO가 비어 있는 신상품 하나가 등록됐다 / CF02 카테고리: 18행
  • CF18_RPT 리포트용 재고표(문제 설정): CTG_NM이 NOT NULL이다 / CF_CLOSE 마감 관리(문제 설정): 날짜마다 한 행 · CLOSE_YN
  • 12월 30일 판매 수량은 매장 여섯 곳을 합쳐 2,140개다

원본 SQL ​

sql
PROCEDURE P_DAY_CLOSE(p_dt VARCHAR2) IS
BEGIN
  -- ① 그날 판매 수량만큼 재고를 뺀다
  UPDATE CF18 s
     SET s.INV_QTY = s.INV_QTY - (SELECT NVL(SUM(d.SLE_QTY), 0)
                                    FROM CF10 h JOIN CF11 d ON d.SLE_NO = h.SLE_NO
                                   WHERE h.SLE_DT = p_dt AND h.SHP_NO = s.SHP_NO AND d.ITM_NO = s.ITM_NO)
   WHERE s.INV_BS_DT = p_dt;

  -- ② 날짜별 백업 테이블
  EXECUTE IMMEDIATE 'CREATE TABLE CF18_BAK_' || p_dt
                 || ' AS SELECT * FROM CF18 WHERE INV_BS_DT = ''' || p_dt || '''';

  -- ③ 리포트용 재고표
  INSERT INTO CF18_RPT (SHP_NO, ITM_NO, INV_BS_DT, INV_QTY, CTG_NM)
  SELECT s.SHP_NO, s.ITM_NO, s.INV_BS_DT, s.INV_QTY, g.CTG_NM
    FROM CF18 s
    JOIN CF01 i ON i.ITM_NO = s.ITM_NO
    LEFT JOIN CF02 g ON g.CTG_NO = i.CTG_NO
   WHERE s.INV_BS_DT = p_dt;

  -- ④ 마감 표시
  UPDATE CF_CLOSE SET CLOSE_YN = 'Y' WHERE CLOSE_DT = p_dt;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;                          -- 실패하면 처음 상태로 (운영 문서)
    RAISE;
END;

실행계획 학습용 예시 · DDL의 암시적 COMMIT은 교재 12화 실측(실습/01 [E]) ​

xplan
[12월 30일 · 31일 실행 기록 — 스케줄러와 알림 로그]
회   시각          단계                                  결과
1    12/30 23:30   ① UPDATE CF18 (408행)                  끝
                   ② CREATE TABLE CF18_BAK_20231230       끝
                   ③ INSERT CF18_RPT                      ORA-01400: cannot insert NULL into ("PUP"."CF18_RPT"."CTG_NM")
                   예외 처리부 ROLLBACK · RAISE           → 스케줄러 FAILED
2    12/31 08:40   (신상품 분류를 채운 뒤) ①               끝
                   ②                                      ORA-00955: name is already used by an existing object
                   예외 처리부 ROLLBACK · RAISE           → FAILED
3    12/31 08:52   (CF18_BAK_20231230 을 DROP 한 뒤) ① ② ③ ④ COMMIT   → SUCCEEDED

[12월 31일 09:10 — 매장 001 · 상품 셋 · CF18 (INV_BS_DT = '20231230')]
ITM_NO    마감 전 재고   그날 판매   지금 INV_QTY   있어야 할 값
1000021        140          22            74             118
1000037         88          15            43              73
1000102         61           9            34              52
  (매장 여섯 곳 합계: 그날 판매 2,140개 · 있어야 할 값보다 덜 남은 재고 4,280개)

[연습 DB 에서 다시 잰 것 — 교재 12화 실습/01 [E] 와 같은 모양]
  INSERT → CREATE TABLE (성공)              → ROLLBACK   INSERT 가 남았다
  INSERT → CREATE TABLE (ORA-00955 로 실패) → ROLLBACK   INSERT 가 남았다

문제 ​

  1. 예외 처리부가 ROLLBACK했는데도 ①이 남은 이유를 실행 기록으로 설명하시오. 두 번째 실행에서는 ②가 실패했는데도 ①이 남은 이유도 쓰시오.
  2. 실패하면 정말 처음 상태로 돌아가고, 다시 돌려도 재고를 두 번 빼지 않도록 프로시저를 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자