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

SERIALIZABLE로 바꿨는데도 거래처 발주 한도가 또 넘쳤다

실전PLSQL유통534 / 700

상황 ​

본사 레거시 유통은 거래처마다 신용 한도를 두고, 입고되지 않은 발주 금액의 합이 한도를 넘으면 발주 등록을 막는다. MD 네 명이 같은 거래처의 발주를 동시에 등록하는 일이 잦다. 지난달 한도 3,000만 원인 거래처 P007의 미입고 발주 합계가 3,412만 원이 되어 거래처가 출고를 멈췄다. 조민우는 "동시에 읽어서 생긴 일"이라며 발주 등록 프로시저를 SERIALIZABLE로 바꿨다. 이번 달에도 같은 거래처가 3,310만 원이 되었다. 에러 로그에 ORA-08177은 한 번도 없다.

테이블과 인덱스 ​

  • CF12 발주: 734행 · 10블록 · CF12_PK (ORD_NO) · PTNR_NO · ORD_PRGS_CD(10 발주완료, 11 입고중, 12 입고완료) · 이 문제에서는 발주 금액 ORD_AMT와 인덱스 IX_CF12_PTNR (PTNR_NO, ORD_PRGS_CD)를 더했다
  • CF12_PTNR 거래처 한도(이 문제를 위해 더한 테이블): PTNR_NO PK · CRDT_LMT 신용 한도 · 거래처 30곳
  • 발주 등록 한 번은 10ms 안쪽이다. 입고가 끝나면 입고 화면이 ORD_PRGS_CD를 12로 바꾼다. 발주 금액을 고치는 「발주 수정」 화면도 있다.

원본 SQL ​

sql
PROCEDURE REG_PO(p_ptnr VARCHAR2, p_no VARCHAR2, p_amt NUMBER) IS
  v_lmt  NUMBER;
  v_open NUMBER;
BEGIN
  SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;      -- 지난달 한도 초과 뒤 추가

  SELECT CRDT_LMT INTO v_lmt FROM CF12_PTNR WHERE PTNR_NO = p_ptnr;

  SELECT NVL(SUM(ORD_AMT), 0) INTO v_open
    FROM CF12
   WHERE PTNR_NO = p_ptnr AND ORD_PRGS_CD IN ('10', '11');

  IF v_open + p_amt > v_lmt THEN
    RAISE_APPLICATION_ERROR(-20041, '거래처 한도를 넘습니다');
  END IF;

  INSERT INTO CF12 (ORD_NO, ORD_DT, PTNR_NO, ORD_PRGS_CD, ORD_AMT)
  VALUES (p_no, TO_CHAR(SYSDATE, 'YYYYMMDD'), p_ptnr, '10', p_amt);
  COMMIT;
END;

실행계획 학습용 예시 ​

xplan
[시간표 — 거래처 P007 · 한도 30,000,000 · 미입고 26,200,000]
시각           MD 1 (SID 52) · 발주 3,800,000                    MD 2 (SID 87) · 발주 3,100,000
14:05:11.300   SET TRANSACTION … SERIALIZABLE
14:05:11.302                                                      SET TRANSACTION … SERIALIZABLE
14:05:11.303   SUM → 26,200,000 · + 3,800,000 = 30,000,000 ≤ 한도
14:05:11.304                                                      SUM → 26,200,000 · + 3,100,000 = 29,300,000 ≤ 한도
14:05:11.305   INSERT 발주 2023121407 · COMMIT
14:05:11.306                                                      INSERT 발주 2023121408 · COMMIT
               → 미입고 33,100,000 (한도 초과) · 기다림 0 · 에러 0

[V$LOCK — 14:05:11.305 (두 세션이 모두 INSERT 한 순간)]
SID   TYPE  대상                  LMODE  REQUEST  BLOCK
52    TM    CF12                    3      0        0
52    TX    52 의 트랜잭션           6      0        0      ← 새 행 2023121407 하나
87    TM    CF12                    3      0        0
87    TX    87 의 트랜잭션           6      0        0      ← 새 행 2023121408 하나
      (두 세션이 같은 행을 고치지 않는다 — 서로 기다릴 일도 ORA-08177 이 날 일도 없다)

[한도 초과가 된 등록]  지난달 34,120,000 (READ COMMITTED 일 때) · 이번 달 33,100,000 (SERIALIZABLE 로 바꾼 뒤)

문제 ​

  1. 시간표와 V$LOCK을 근거로 두 등록이 모두 한도 검사를 통과한 이유와, SERIALIZABLE이 이를 막지 못한 이유를 설명하시오.
  2. 한도를 넘는 발주가 들어가지 않도록 고치는 방법을 둘 이상 비교하고, 하나를 골라 SQL로 작성하시오. 고른 방법의 대가도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자