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

주문에 상품을 추가할 때 가끔 상세번호 중복 에러가 난다

중급INSERT한빛유통501 / 700

상황 ​

한빛유통 콜센터의 「주문 수정」 화면에서는 상담원이 통화 중인 고객의 주문에 상품을 추가한다. 하루 수천 번 쓰는 화면인데, 하루 스무 번쯤 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건
  • 상세번호는 주문마다 1, 2, 3 …으로 매긴다. 배송 전표와 고객 화면에 그대로 찍힌다
  • 같은 주문을 동시에 고치는 일은 드물다. 다른 주문끼리는 상담원 수십 명이 동시에 고친다

원본 SQL ​

sql
-- 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;

실행계획 학습용 예시 ​

xplan
[번호 받기 — 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

문제 ​

  1. 번호를 받는 SQL은 3블록짜리이고 인덱스도 제대로 탄다. 그런데도 중복이 나는 이유를 시간표로 설명하시오.
  2. 상세번호를 주문마다 1, 2, 3으로 매기는 규칙은 지키면서 중복이 나지 않게 고치시오. 개발팀의 두 안이 왜 답이 아닌지도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자