테마
한빛유통 고객센터 상담원은 「주문 수정」 화면에서 주문상세의 수량을 고친다. 화면은 상세를 먼저 고치고 주문 금액을 다시 계산한 뒤 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_AMTT_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건 안팎이 들어온다데드락은 한 건만 버리고 다음 건으로 (재시도는 다음 회차)이다.-- 「주문 수정」 화면 (상담원): 상세 먼저, 주문 금액 나중
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;
[시간표 — 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' ← 다음 회차에 다시 잡히지 않는다