테마
한빛유통 콜센터의 「주문 수정」 화면에서는 상담원이 통화 중인 고객의 주문에 상품을 추가한다. 하루 수천 번 쓰는 화면인데, 하루 스무 번쯤 ORA-00001: unique constraint (SQLP.PK_T_ORD_DET) violated가 난다.
로그를 보면 같은 주문에 0.1초 간격으로 요청 두 개가 들어온 경우다. 상담원이 「추가」를 두 번 눌렀거나, 같은 주문을 두 상담원이 동시에 고쳤다.
개발팀은 두 가지 안을 냈다. ① 상세번호를 시퀀스로 받는다. ② 번호를 받기 전에 LOCK TABLE T_ORD_DET IN EXCLUSIVE MODE를 건다.
T_ORD 주문: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · ORD_AMT는 상세 금액의 합계T_ORD_DET 주문상세: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · 주문당 1 ~ 3건-- PKG_ORD.ADD_ITEM — 「추가」 버튼 한 번
SELECT NVL(MAX(ORD_DET_NO), 0) + 1 INTO v_det_no
FROM T_ORD_DET
WHERE ORD_SEQ = :ord_seq;
INSERT INTO T_ORD_DET (ORD_SEQ, ORD_DET_NO, ITM_ID, ORD_QTY, UNT_PRC)
VALUES (:ord_seq, v_det_no, :itm_id, :qty, :prc);
UPDATE T_ORD SET ORD_AMT = ORD_AMT + :qty * :prc
WHERE ORD_SEQ = :ord_seq;
COMMIT;
[번호 받기 — ALLSTATS LAST]
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 3 |
| 2 | FIRST ROW | | 1 | 1 | 1 |00:00:00.01 | 3 |
|* 3 | INDEX RANGE SCAN (MIN/MAX)| PK_T_ORD_DET | 1 | 1 | 1 |00:00:00.01 | 3 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("ORD_SEQ"=:ORD_SEQ)
[시간표 — 주문 58214 · 「추가」 두 번]
시각 요청 1 (SID 44) 요청 2 (SID 97)
10:12:03.10 MAX+1 → 3 · INSERT (58214, 3)
10:12:03.18 MAX+1 → 3 (44 의 행은 COMMIT 전이라 안 보인다)
INSERT (58214, 3) → 기다림 (TX REQUEST 4)
10:12:03.21 UPDATE T_ORD · COMMIT → ORA-00001 · 화면 "저장 실패, 다시 시도하세요"
[개발팀 안 ① 시퀀스] 상세번호가 58214-3 이 아니라 58214-200117 처럼 찍힌다
[개발팀 안 ② LOCK TABLE] 번호를 받은 트랜잭션이 끝날 때까지 다른 주문의 상세 INSERT 가 전부 enq: TM - contention