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

취소된 주문의 상세가 그대로 남았고, 그날 주문 수정 화면은 4분을 기다렸다

중급PLSQL한빛유통515 / 700

상황 ​

한빛유통 고객센터 상담원은 「주문 수정」 화면에서 주문상세의 수량을 고친다. 화면은 상세를 먼저 고치고 주문 금액을 다시 계산한 뒤 COMMIT한다. 같은 시각 「결제 취소 반영」 배치가 5분마다 PG사의 취소 통보 300건 안팎을 처리한다. 배치는 통보마다 PG사에 취소를 확인하고, 주문을 CANCEL로 바꾸고, 상세 수량을 0으로 만든다. COMMIT은 300건을 다 처리한 뒤 한 번 한다(약 4분). 7월 2일 10:15에 상담원 화면이 4분 동안 멈췄다. 다음 날 정산팀 조민우가 알려 왔다. "주문은 CANCEL인데 상세 수량은 그대로인 주문이 하나 있어요."

테이블과 인덱스 ​

  • T_ORD 주문: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · ORD_ST · ORD_AMT
  • T_ORD_DET 주문상세: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · 주문당 1~3건
  • T_PG_CNCL PG 취소 통보(이 문제의 작업 테이블): ORD_SEQ · RCV_DT · DONE_YN · 5분마다 300건 안팎이 들어온다
  • 배치의 예외 처리는 2019년에 강도현이 넣었다. 주석은 데드락은 한 건만 버리고 다음 건으로 (재시도는 다음 회차)이다.

원본 SQL ​

sql
-- 「주문 수정」 화면 (상담원): 상세 먼저, 주문 금액 나중
UPDATE T_ORD_DET SET ORD_QTY = :qty
 WHERE ORD_SEQ = :ord_seq AND ORD_DET_NO = :det_no;
UPDATE T_ORD
   SET ORD_AMT = (SELECT SUM(ORD_QTY * UNT_PRC) FROM T_ORD_DET WHERE ORD_SEQ = :ord_seq)
 WHERE ORD_SEQ = :ord_seq;
COMMIT;

-- 「결제 취소 반영」 배치 (5분마다): 주문 먼저, 상세 나중 · 300건을 다 처리하고 COMMIT
DECLARE
  e_deadlock EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_deadlock, -60);
BEGIN
  FOR r IN (SELECT ORD_SEQ FROM T_PG_CNCL WHERE DONE_YN = 'N' ORDER BY RCV_DT) LOOP
    PKG_PG.CHECK_CANCEL(r.ORD_SEQ);                        -- PG사 취소 확인 (건당 0.5 ~ 1초)
    BEGIN
      UPDATE T_PG_CNCL SET DONE_YN = 'Y'     WHERE ORD_SEQ = r.ORD_SEQ;    -- 받은 통보 표시
      UPDATE T_ORD     SET ORD_ST = 'CANCEL' WHERE ORD_SEQ = r.ORD_SEQ;
      UPDATE T_ORD_DET SET ORD_QTY = 0       WHERE ORD_SEQ = r.ORD_SEQ;
    EXCEPTION
      WHEN e_deadlock THEN
        PKG_LOG.WRITE('DEADLOCK ' || r.ORD_SEQ);           -- 데드락은 한 건만 버리고 다음 건으로 (재시도는 다음 회차)
    END;
  END LOOP;
  COMMIT;
END;

실행계획 학습용 예시 · ORA-00060의 되돌림 범위는 교재 12화 실측 ​

xplan
[시간표 — 7월 2일 · 주문 88213 (상세 3건)]
시각           상담원 화면 (SID 88)                         결제 취소 반영 배치 (SID 41)
10:15:20.0     UPDATE T_ORD_DET (88213, 2번) → 잠금
10:15:20.3                                                 DONE_YN = 'Y' (88213) · UPDATE T_ORD (88213) → 잠금
10:15:20.3                                                 UPDATE T_ORD_DET (88213) → 1번 행 잠금 · 2번 행에서 기다림 (SID 88)
10:15:21.0     UPDATE T_ORD (88213) 주문 금액 → 기다림 (SID 41)
10:15:23.3                                                 ORA-00060 (먼저 기다린 쪽) → 로그 'DEADLOCK 88213' → 다음 통보로
10:15:23.3     계속 기다림 (41 이 T_ORD 88213 을 쥐고 있다)
10:19:27                                                   300건 끝 · COMMIT
10:19:27       깨어남 → 주문 금액 갱신 · COMMIT (4분 6초)

[V$SESSION — 10:17:00]
SID   EVENT                            SECS   BLOCKING_SESSION   하고 있는 문장
88    enq: TX - row lock contention      99   41                 UPDATE T_ORD SET ORD_AMT = (SELECT SUM(ORD_QTY * UNT_PRC) …
41    TCP Socket (KGAS)                   0                      PKG_PG.CHECK_CANCEL (다른 통보 · PG사 응답 대기)

[다음 날 — 주문 88213]
T_ORD       ORD_ST = 'CANCEL' · ORD_AMT = 47,000 (화면이 다시 계산했다)
T_ORD_DET   1번 2개 · 2번 3개 (화면이 고친 값) · 3번 1개    ← 취소 수량 0 이 반영되지 않았다
T_PG_CNCL   DONE_YN = 'Y'                                    ← 다음 회차에 다시 잡히지 않는다

문제 ​

  1. 시간표를 근거로 두 프로그램 사이에 원이 어떻게 생겼는지, 데드락 에러가 난 뒤에도 화면이 4분을 더 기다린 이유를 설명하시오.
  2. 주문 88213의 데이터가 어긋난 채 남은 이유를 쓰고, 화면과 배치를 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자