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

타임딜 10시 정각, 주문하기를 누른 화면이 몇 분씩 돌다가 사이트 전체가 멈췄다

실전PLSQL이커머스519 / 700

이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.

상황 ​

이커머스 플랫폼은 매주 수요일 10:00에 한 상품을 반값에 파는 타임딜을 연다(재고 500개). 지난 수요일 10:00:05부터 「주문하기」를 누른 고객의 화면이 몇 분씩 돌았고, 10:03에는 타임딜과 상관없는 상품 목록과 로그인까지 멈췄다. 10:12에 운영팀이 WAS를 재시작했다. 그날 타임딜 상품은 500개 중 14개만 팔렸다. 주문 처리는 재고를 먼저 빼고, PG 결제창을 띄워 고객이 결제를 마칠 때까지 기다렸다가 COMMIT한다. 결제창은 평균 40초 걸리고, 고객이 창을 닫으면 3분 뒤 타임아웃으로 ROLLBACK한다.

테이블과 인덱스 ​

  • product_stock 상품 재고(이 문제의 파생 테이블): products와 1:1 · 280행 · 4블록 · PK product_id · stock_qty
  • orders: 34,908행 · 193블록 · PK만 · status (PENDING · PAID · CANCELLED …)
  • order_items: 73,498행 · 290블록 · PK만
  • payments: 34,908행 · 192블록 · PK만 · order_id와 1:1
  • stock_hold 재고 선점(새로 만들어도 되는 테이블): hold_id · product_id · customer_id · qty · status · expire_at
  • WAS 커넥션 풀 최대 200개 · 타임딜 첫 1분에 「주문하기」 3,100번

원본 SQL ​

sql
-- PKG_ORDER.PLACE: 「주문하기」부터 결제 완료까지 한 트랜잭션
UPDATE product_stock
   SET stock_qty = stock_qty - :qty
 WHERE product_id = :pid
   AND stock_qty >= :qty;                                      -- 0건이면 품절
INSERT INTO orders (order_id, customer_id, order_date, status, total_amount)
VALUES (:oid, :cid, SYSDATE, 'PENDING', :amt);
INSERT INTO order_items (order_item_id, order_id, product_id, quantity, unit_price)
VALUES (:oiid, :oid, :pid, :qty, :price);

-- PG 결제창을 띄우고 고객의 결제를 기다린다 (평균 40초 · 창을 닫으면 3분 뒤 타임아웃 → ROLLBACK)

INSERT INTO payments (payment_id, order_id, payment_method, amount, paid_at)
VALUES (:payid, :oid, :method, :amt, SYSDATE);
UPDATE orders SET status = 'PAID' WHERE order_id = :oid;
COMMIT;

실행계획 학습용 예시 ​

xplan
[시간표 — 수요일 10:00 · 타임딜 상품 product_id 117 (재고 500)]
시각        고객 1 (SID 1204)                          고객 2 ~ 3,100
10:00:01    UPDATE product_stock (117) → 499 · 행 잠금
            결제창 — COMMIT 전
10:00:01 ~                                             UPDATE product_stock (117) → 기다림 (고객 1 의 TX)
10:00:44    결제 완료 · COMMIT                          고객 2 깨어남 → 498 · 결제창 … 나머지는 계속 기다림
10:01:30                                               고객 4 결제창을 닫음 → 3분 동안 행을 쥔 채 (타임아웃 대기)
10:03:00    커넥션 풀 200개가 전부 이 UPDATE 에서 기다린다 → 다른 화면도 커넥션을 얻지 못한다
10:12       WAS 재시작 → 기다리던 트랜잭션 전부 ROLLBACK · 그때까지 팔린 것 14개

[V$SESSION — 10:02:30]
SID    EVENT                            SECS   BLOCKING_SESSION   하고 있는 문장
1311   SQL*Net message from client        52                      (결제창 — 트랜잭션을 연 채)
1204 … enq: TX - row lock contention    149   1311                UPDATE PRODUCT_STOCK SET STOCK_QTY = STOCK_QTY - :B1 …
       (기다리는 세션 196개 · 모두 BLOCKING_SESSION 1311 · 모두 같은 문장)

[V$LOCK — 같은 시각]
SID    TYPE  대상                     LMODE  REQUEST  BLOCK
1311   TM    PRODUCT_STOCK              3      0        0
1311   TX    SID 1311 의 트랜잭션         6      0        1
그 밖  TX    SID 1311 의 트랜잭션         0      6        0      × 196   ← 같은 행 하나를 원한다

[WAS 로그]  10:03 ~ 10:12 커넥션 대기 타임아웃 18,420건 (상품 목록 · 로그인 · 장바구니 포함)

문제 ​

  1. 시간표와 V$SESSION, V$LOCK을 근거로 재고 행 하나가 어떻게 사이트 전체를 멈추게 했는지 설명하시오.
  2. 재고를 결제 전에 짧게 선점하는 방법과 결제가 끝난 뒤에 빼는 방법을 비교하고, 하나를 골라 SQL로 작성하시오.
  3. 고른 방법의 대가(팔지 못하거나 결제를 취소해야 하는 경우)와 그 처리를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자